AUTH4D-214 Passkeys, passwords and SMS codes on device approval and guest upgrade
Game (native) applications can now offer passkeys (AUTH4D-143 (repository file docs/api-spec/auth4d-143.md)), passwords
(AUTH4D-144 (repository file docs/api-spec/auth4d-144.md)) and SMS codes (AUTH4D-145 (repository file docs/api-spec/auth4d-145.md)) in two places:
- on the device approval page (
/login/device), to sign the player in before they approve a code; - on the guest upgrade page (
/login/upgrade), to save a guest’s progress. This covers both the native upgrade intent (AUTH4D-67 (repository filedocs/api-spec/auth4d-67.md)) and the browser upgrade window.
Each method is still opt-in per application, with the same console settings as for browser
applications. No new endpoints are added. The existing password, SMS and passkey endpoints accept
two more kinds of browser flow, and the device resume and guest upgrade endpoints accept more proofs.
Tokens still reach the device only through the device-code grant (AUTH4D-16 (repository file docs/api-spec/auth4d-16.md)), and a
guest upgrade still never merges two accounts.
Verification data
For decision: "login_required", login_methods in GET /device/verify gains four optional fields.
Each is present (and true) only when it applies, so earlier responses still parse:
| Field | When |
|---|---|
passkey |
The application has passkeys turned on |
passkey_sign_up |
As passkey, and the application and the project both allow sign-up |
password |
The application opted in to password sign-in and email sign-in is on |
sms |
The application has SMS turned on and the deployment has an SMS provider |
With sms, the response also has a top-level sms_countries array of ISO 3166-1 alpha-2 codes, as
on the login context:
{
"decision": "login_required",
"login_methods": {
"email": true,
"discord": true,
"password": true,
"sms": true,
"passkey": true
},
"sms_countries": ["GB", "ZA"]
}
The fields are in DeviceVerifyResponseSchema. A browser session that can already approve
(decision: "approvable") gets no login_methods, as before.
Login context for guest linking
In a game’s guest-linking flow, GET /t/{tenantId}/login/context now reports password, sms (with
sms_countries), passkey and passkey_sign_up under the same rules as for a browser application.
A browser application’s guest-linking flow now reports passkey and passkey_sign_up too, and SMS
is no longer limited to its authorization flows. Single sign-on stays off in every guest-linking
flow.
Flows the endpoints accept
The request bodies, rate limits and error answers of these endpoints are unchanged. Each still
needs Origin equal to the auth origin and the __Host-auth4_flow_{flowId} cookie of the named
flow; a missing or mismatched cookie is 403 access_denied.
| Endpoints | Browser application | Game application |
|---|---|---|
/password/sign-in, /password/reset/start, /password/reset/confirm, /password/change |
Authorization and guest-linking flows | Device sign-in flow and its own guest-linking flow |
/sms/verification/start, /sms/verification/confirm |
Authorization and guest-linking flows | Device sign-in flow and its own guest-linking flow |
/passkeys/registration/options, /passkeys/registration/verify |
Authorization and guest-linking flows | Device sign-in flow and its own guest-linking flow |
/passkeys/authentication/options, /passkeys/authentication/verify |
Authorization flows | Device sign-in flow |
All paths are under /t/{tenantId}. A device sign-in flow is the flow named by browser_flow_id
in the verify response (AUTH4D-59 (repository file docs/api-spec/auth4d-59.md)). It must belong to the same tenant and game
application, and its code must still be pending. A game application never reaches these endpoints
through an authorization flow.
Passkeys have two more limits:
- In device sign-in and guest-linking flows,
POST /passkeys/registration/optionsonly creates an account (noproof_id). Adding a passkey to an account that is already signed in stays limited to browser applications’ authorization flows. A body withproof_idis403 access_denied. - Guest-linking flows don’t run passkey sign-in:
POST /passkeys/authentication/optionsis403 access_deniedthere, because a passkey that already has an account can’t upgrade a guest.
Passkey sign-up still needs the application’s allow_sign_up and project sign-up, and the
pre_registration Flows hook still runs. A game application with passkeys off, or a call from the
wrong kind of flow, is 403 access_denied.
Device resume
POST /device/sign-in/resume (AUTH4D-120 (repository file docs/api-spec/auth4d-120.md)) now also accepts these proofs for the
same device sign-in flow:
proof_purpose |
Issued by |
|---|---|
email_verification |
Password sign-in, a password reset or change |
sms_verification |
/sms/verification/confirm |
passkey_login |
/passkeys/authentication/verify, or registration with sign-up |
The request, response and error table are unchanged. The proof is one-time and bound to the tenant,
client and browser_flow_id. Redeeming it creates the browser session; the player then approves or
denies the code as before. Email codes still sign in directly through
/email/verification/confirm, without a resume.
Guest upgrade
POST /t/{tenantId}/guest/upgrade and POST /t/{tenantId}/guest/upgrade-intent/complete now link
two more proofs. Password proofs are email proofs, so they link the email identity as before.
| Proof | What is linked |
|---|---|
sms_verification |
The phone identity of the verified number. A number that belongs to another account is 409 identity_in_use. |
passkey_login |
The passkey just created in this guest-linking flow moves to the guest user. A passkey that belongs to another account is 409 identity_in_use. |
The passkey moves only when its account was created after the linking intent, owns nothing but
that one passkey (no identity, session, membership or consent), and the guest has fewer than 10
passkeys. The emptied account is then disabled. Its WebAuthn user handle stays with the credential,
so the authenticator can use the passkey after the move. Migration
0034_passkey_user_handle.sql stores that stable handle. A passkey is not an identity, so the
response’s identity names the guest’s own identity:
{ "status": "approved", "identity": { "identity_id": "idn_…", "provider": "guest" } }
A user with a passkey is no longer a guest, so the next access token drops auth4_guest. The
pre_guest_upgrade Flows hook gets method phone or passkey. The guest.upgraded audit event
and Flows event carry the same value in provider and data.method. A passkey upgrade’s Flows
context names no provider.
A password set on the upgrade page goes through /password/reset/confirm. If the address already
belongs to another account, that account’s password is set or reset as on the sign-in page
(AUTH4D-211 (repository file docs/api-spec/auth4d-211.md) applies), and the upgrade then answers 409 identity_in_use.
Hosted pages
/login/device shows a method choice when any of password, SMS, passkey or a social provider is
offered: Continue with email, Continue with a password, Continue with a text message,
one button per social provider, Sign in with a passkey and, with passkey_sign_up, Create an
account with a passkey. Passkey buttons are hidden when the browser doesn’t support passkeys.
After a password, SMS or passkey step the page calls the resume endpoint, then shows the usual
Connect {application}? step.
/login/upgrade adds Create a password (an email code and a new password), Link a mobile
number and Create a passkey (only with passkey_sign_up), each when the application offers
it. Every method still ends with the explicit link confirmation.
Management
PATCH /management/tenants/{tenantId}/clients/{clientId}/password-sign-inaccepts game applications. Only server clients answer400 validation_failed. Migration0033_native_password_sign_in.sqladdsclients.native_password_enabledfor them, becausepassword_enabledis limited to browser applications by a check constraint.PUT .../clients/{clientId}/smsaccepts game applications.GETandPATCH .../clients/{clientId}includepasskeysfor game applications too.400 passkeys_unsupportedis no longer returned.
Source: docs/api-spec/auth4d-214.md