AUTH4D-332 email claims on guest upgrades and refresh
No new routes or wire shapes. Two token paths now follow the AUTH4D-265 (repository file docs/api-spec/auth4d-265.md) rule:
email and email_verified: true appear only with the email scope and a provider email
identity whose address auth4.dev verified. sub never changes.
Guest upgrades
POST /t/{tenantId}/guest/upgrade and the native intent’s POST /t/{tenantId}/guest/upgrade-intent/token (and their verified email step completions,
auth4d-302.md (repository file docs/api-spec/auth4d-302.md)) no longer copy the linked identity’s address into the replacement
session. A guest upgraded through Discord, Google, Apple, Microsoft, Epic or an SSO connection gets
no email claim from that provider, even when the provider marked it verified.
- The replacement ID token, and userinfo with its access token, carry
idp: the method used for the upgrade (discord,google,sso,steam,emailand so on). - Guest sessions are granted
openid,profileandoffline_accessonly, neveremail, so an upgraded session carries no address claim. The address is stored on the account; the player’s next sign-in with theemailscope gets it.
Refresh
POST /oauth/token with grant_type=refresh_token reads the user’s verified email identity again
instead of re-emitting the address stored with the session at sign-in, the way roles and metadata
are re-read (auth4d-154.md (repository file docs/api-spec/auth4d-154.md)).
- An address changed through the add-email intent (auth4d-149.md (repository file
docs/api-spec/auth4d-149.md)) or its change path shows up in the next refreshed ID token. - A removed address, or one moved to another user, is left out of the next refreshed ID token.
- The refreshed ID token and userinfo with the refreshed access token give the same
email,email_verifiedandidp.idpstays the method of the original sign-in. - A failed lookup answers
400 invalid_grantbefore rotation, so the presented refresh token stays usable.
Tests
tests/integration/auth4d-332.email-claims.test.ts: browser upgrades through Discord, Google and
SSO and a native Discord upgrade (with a refresh) emit idp and no upstream address; a refresh
after the address is changed, removed, moved to another user or marked unverified carries the
current address or none, and agrees with userinfo.
Source: docs/api-spec/auth4d-332.md