Sign-in (SSO)
People sign in to your app with their email and a one-time code from Werify. Your server then links them to their Moat page.
The browser carries the result. Your server decides. Treat anything that arrives from the browser as a claim until your server has confirmed it with Werify. Only then call Moat with your API key.
The flow#
- Your page opens the Werify sign-in. The person enters their email and the code they receive.
- Werify's
onSuccesscallback gives your page the verified result. - Your page sends that result to your server.
- Your server confirms it with Werify. Don't create a session from browser data alone.
- Your server calls
create_profilewith the verified email and stores the returned keys. - You create your own session and render the person's Moat card.
Browser ── email + code ──▶ Werify
Browser ◀── onSuccess(result) ── Werify
Browser ── result ──▶ Your server ── confirm ──▶ Werify
Your server ── create_profile (X-API-Key) ──▶ Moat
Your server ◀── public_key, private_key ── Moat
Browser ◀── your session + cardIn the browser#
<script src="https://werify.ai/login/v2/werify.js"></script>
<script>
Werify.init({
site_name: "Your App",
site_logo: "https://yourapp.example/logo.png",
onSuccess: async (result) => {
const res = await fetch("/auth/moat-signin", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "same-origin",
body: JSON.stringify(result),
});
if (res.ok) location.href = "/welcome";
},
});
</script>
<button type="button" onclick="Werify.login()">Sign in</button>On your server#
// /auth/moat-signin
$claim = json_decode(file_get_contents('php://input'), true);
// 1. Confirm the claim with Werify before trusting it (see Werify's docs).
$verified = werify_confirm($claim); // your helper, returns the verified email or null
if (!$verified) { http_response_code(401); exit; }
// 2. Link to Moat: get the existing page or create a starter one.
$ch = curl_init(MOAT_BASE_URL . '/profile/api/create_profile');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . MOAT_XSITE_API_KEY],
CURLOPT_POSTFIELDS => http_build_query([
'email' => $verified,
'site_url' => 'https://yourapp.example',
'site_name' => 'Your App',
'callback_site_param' => 'yourapp',
'user_update_path' => 'https://yourapp.example/hooks/moat',
]),
]);
$moat = json_decode(curl_exec($ch), true);
// 3. Store keys against your user. The private key never goes to the browser.
save_user_keys($verified, $moat['public_key'], $moat['private_key']);
start_session_for($verified);Rules that keep people safe#
- Identify people by the email Werify verified, confirmed on your server. Never by an email typed into a form.
- Keep
MOAT_XSITE_API_KEYand each person'sprivate_keyon your server only. - Protect your sign-in endpoint against cross-site requests with a same-site cookie or CSRF token.
- When you receive
profile.deletedorprofile.unlinked, drop the stored keys.
Sending people to edit on Moat#
To let someone edit their full page, link them to Moat with a return address. After saving, Moat shows a Go back button that returns them to your app.
https://moat.page/profile/profile?back=https%3A%2F%2Fyourapp.example%2Fprofile
For editing inside your app instead, use the in-place editor.