Changelog
Getting Started

Register a confirmed account

SPA password and passkey signup when RequireConfirmedAccount is true.

Build a first-party SPA signup that stays logged out until the user confirms email, then signs in. Use this when RequireConfirmedAccount is true (the facade default).

Assume the cookie facade: management and cookie login under /identity, passkeys under /account. See Quick start and Recipes.

Shared setup

  1. Offer Password and Passkey on the same signup screen.
  2. Call GET /identity/csrfToken before unsafe POSTs that require antiforgery. Send the value as header RequestVerificationToken.
  3. After a successful register (200), show the same Check your email screen for both methods. Do not treat that 200 as signed in.
  4. Do not tell the user the email is already registered. Duplicate-email paths return generic success or a generic failure (see below).

Password register

  1. Collect email and password.
  2. POST /identity/register with body { "email", "password" } and CSRF.
  3. On 200, show Check your email. Stay logged out. Duplicate email also returns 200 (anti-enumeration). Use the same copy.
  4. On validation failure, show the 400 problem details.
  5. The mail link calls GET /identity/confirmEmail?userId=…&code=….
    • With EmailConfirmation.ConfirmEmailRedirectUri unset: success is plain-text thank-you (200); failure is 401.
    • With the URI set: success and failure return 302 to that URI with status=confirmed|failed and flow=confirm (or flow=change-email when changedEmail is present). Configure the option under Email confirmation.
  6. After confirm, sign in: POST /identity/login with email and password. Use ?useSessionCookies=false only when you need a persistent cookie. See Cookie auth.
  7. On login success, enter the app with the session cookie (credentials: "include").

Passkey register

  1. Collect email only.
  2. POST /account/passkeys/register/options with { "email" } and CSRF. Creation options are returned even when the email is already taken.
  3. Run navigator.credentials.create(…). If the user cancels, show a soft UI error. Do not call register.
  4. POST /account/passkeys/register?useCookies=true with { "email", "credentialJson" } and CSRF.
  5. New email success: 200 with { "credentialId" } (or equivalent PasskeyCredentialResponse). No Identity application cookie while the account is unconfirmed. Show the same Check your email screen. You may add a short note that they registered with a passkey.
  6. Email taken, bad ceremony, or an attempt to attach to an existing user id: generic 400 "Unable to complete registration." Use that copy. Do not invent a more specific reason.
  7. Confirm with the same mail link and GET /identity/confirmEmail behavior as password register. The passkey is already stored on the unconfirmed user.
  8. After confirm, sign in with passkey:
    1. POST /account/passkeys/requestOptions (optional ?username=).
    2. navigator.credentials.get(…).
    3. POST /account/passkeys/login?useCookies=true with the assertion payload and CSRF.
  9. Login succeeds only after confirm. Unconfirmed login returns 401 Invalid credentials.

Default passkey completer cookie flags follow Identity Login, not facade LoginCookie. See Passkeys.

Host checklist

DoDo not
Same check-email screen for password and passkeyTreat register 200 as logged in
Generic copy on duplicate emailSay "email already registered"
Sign in only after confirmExpect a session from unconfirmed passkey register

Out of scope

This page does not cover sign-in inside confirmEmail, public resend without a session, or OAuth.