AUTH4D-269 verified email step on the hosted device approval page

Native games sign in through device authorization (auth4d-16.md (repository file docs/api-spec/auth4d-16.md)). When the player signs in on the hosted device page with Steam (or another social method) and the application’s requireVerifiedEmail (auth4d-260.md (repository file docs/api-spec/auth4d-260.md)) applies, the page runs the same verified email step as hosted login (auth4d-263.md (repository file docs/api-spec/auth4d-263.md), auth4d-264.md (repository file docs/api-spec/auth4d-264.md)) before the browser is signed in. The device code can only be approved once the step is complete. The tokens the device then polls carry email, email_verified: true and idp. The design is in docs/architecture/verified-email-step.md (repository file docs/architecture/verified-email-step.md).

Guest upgrades run the step since AUTH4D-302 (auth4d-302.md (repository file docs/api-spec/auth4d-302.md)).

POST /device/sign-in/resume

The request is unchanged. When the redeemed proof’s sign-in needs the step, the proof is still consumed, but no browser session is created. The answer (DeviceSignInResumeResponseSchema) is:

{
  "status": "verify_email",
  "user_code": "ABCD1234",
  "interaction": { "type": "verify_email", "step": "est_…", "expires_in": 600 }
}

It sets the __Host-auth4_email_step cookie and leaves the device sign-in flow cookie (__Host-auth4_flow_{browser_flow_id}) in place. The step is stored in the tenant’s Durable Object for ten minutes, bound to the device sign-in flow and the user. Every other answer is as before; signed_in still means the browser is signed in.

Step endpoints

POST /t/{tenantId}/email-step/{state,send,resend,confirm,abandon} take the native application’s client_id. A device step answers only for that native application, and an authorization step only for a browser application; any other pairing answers 401 unauthorized_client.

  • confirm proves the code and attaches the address (or links it under after_code) as in auth4d-264.md (repository file docs/api-spec/auth4d-264.md). It then signs the browser in for the device sign-in flow as the final user. It needs the flow cookie as well as the step cookie. The answer is 200 {"status": "signed_in", "user_code": "ABCD1234"} (DeviceEmailStepSignedInSchema). It sets __Host-auth4_session and clears the step and flow cookies. Nothing is approved yet: the page shows the approval and the player confirms it with /device/approval.

    Status Body When
    400 {"error":"expired_token"} The device code expired or was used meanwhile; the address stays saved
    403 {"error":"access_denied"} No flow cookie, the application was disabled or the step still applies
  • abandon ends the step without approving or denying the code. The device keeps polling and gets authorization_pending. The answer is {"redirect_to": "{AUTH_ORIGIN}/login/device?tenant_id=…&client_id=…"}, with the step cookie cleared. restart is accepted and ignored. The hosted page shows its sign-in methods again for the same code in place.

GET /device/verify and POST /device/approval

A browser session whose user still owes the step can’t approve the code. /device/verify answers decision: "login_required" with the sign-in methods, and /device/approval answers 401 {"error":"login_required"}. This covers a session from elsewhere, or one created before the application turned the step on. A session the device page signed in uses the facts of its own sign-in (method, upstream email status). Any other session is judged as a reused session, as /oauth/authorize/continue does. /device/denial never needs an address.

Device-grant tokens

When the device polls /oauth/token, the tokens follow the AUTH4D-265 rule. With the email scope and an Auth4-verified address, the ID token carries the lower-cased email and email_verified: true, and it never carries email: null or email_verified: false. When the device page signed the approving browser in, the ID token carries the idp of that sign-in (for example steam). sub never changes.

Audit

The step writes the same email_step.* events as on hosted login, with the native client_id and the sign-in’s idp, and no code or whole address.

Source: docs/api-spec/auth4d-269.md