Product

How it works Widgets Compare HAPI score

Who it's for

Professionals Companies Services

More

Developers Pricing Journal
Claim your pageSign in

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#

  1. Your page opens the Werify sign-in. The person enters their email and the code they receive.
  2. Werify's onSuccess callback gives your page the verified result.
  3. Your page sends that result to your server.
  4. Your server confirms it with Werify. Don't create a session from browser data alone.
  5. Your server calls create_profile with the verified email and stores the returned keys.
  6. 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 + card

In 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_KEY and each person's private_key on your server only.
  • Protect your sign-in endpoint against cross-site requests with a same-site cookie or CSRF token.
  • When you receive profile.deleted or profile.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.