v150 · Origin Trial · Identity

Registration Flow

Walk through a complete EVP-powered account registration: the user enters their email, Chrome's browser UI asks the mail provider for a cryptographic proof, the page receives a signed token, and the server validates it — all without sending a one-time code. Click through each step to see what the browser shows and what the code does.

Origin trial flow. EVP requires Chrome 150 origin trial + a participating mail provider. This demo uses the local backend to issue and validate a signed token, while the browser consent UI is represented inline because the origin-trial API may be unavailable.
1User fills out registration form
Traditional flow would send an OTP email here. With EVP, no email is sent at this stage.
2Chrome shows provider consent prompt

Chrome intercepts the email address and, if it recognises a participating EVP provider (here: gmail.com), shows a consent dialog. The user is not redirected — this is an in-browser modal.

myapp.example.com
Verify email address?
myapp.example.com wants to confirm you own this address. Your mail provider will sign a proof — no password or OTP required.
3Page calls the EVP API

The registration page called the EVP API before showing the modal. The browser waited for the user's consent, then returned the signed token as the resolved promise value.

// Page code — called before the prompt appeared: const nonce = crypto.randomUUID(); // generate once per registration const token = await navigator.identity.get({ providers: [{ configURL: 'https://accounts.google.com/.well-known/evp-configuration', clientId: 'myapp.example.com', nonce, }] }); // token is a signed JWT — send it to your server await fetch('/api/register', { method: 'POST', body: JSON.stringify({ email: '', token, nonce }), });
4Signed token returned to page

The EVP token is a compact JWT. The page should send it to the server immediately — it is short-lived (minutes). The server validates signature, audience, expiry, and nonce before trusting the email.

// Sample token structure (decoded): { "header": { "alg": "RS256", "typ": "JWT" }, "payload": { "iss": "https://accounts.google.com", "aud": "myapp.example.com", "sub": "alice@gmail.com", "email": "alice@gmail.com", "email_verified": true, "nonce": "…", "iat": 1748..., "exp": 1748... (+300s) } }
5Server validates the token
// Server-side validation (Node.js / Deno example) import { jwtVerify, createRemoteJWKSet } from 'jose'; const JWKS = createRemoteJWKSet( new URL('https://accounts.google.com/.well-known/jwks.json') ); const { payload } = await jwtVerify(token, JWKS, { issuer: 'https://accounts.google.com', audience: 'myapp.example.com', // must match your origin }); // Manual checks after cryptographic verification: if (!payload.email_verified) throw new Error('email not verified'); if (payload.nonce !== sessionNonce) throw new Error('nonce mismatch'); if (usedNonces.has(payload.nonce)) throw new Error('nonce replayed'); usedNonces.add(payload.nonce); // All checks passed — create the account const email = payload.email; // trusted await db.createUser({ email, name: payload.sub });

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗