Tokens and user info Developer
Last updated Aug 15, 2026
Tokens and user info
This page covers how to validate the ID Token after a successful token exchange, how to use the Access Token, and how to read claims from UserInfo.
Token types
| Token | Form (portal apps) | Use |
|---|---|---|
id_token | JWT, ES256 signed | Proves to the client that the user authenticated; includes pairwise sub and related claims |
access_token | JWT, ES256 signed | Call UserInfo and other protected resources; verify with JWKS |
refresh_token | Opaque token (confidential clients only) | Refresh the Access Token |
id_token_signing_alg_values_supported in the discovery document should include ES256. Public keys for verification:
GET {ISSUER}/oauth2/jwks
Cache JWKS and pick a key by kid. Fetch JWKS again when keys rotate.
ID Token validation (required)
At minimum, check:
- Signature: ES256 + JWKS.
iss: equals your Issuer (for examplehttps://api-passport.swaymoon.com).aud: includes yourclient_id.exp/iat: the token is not expired; allow a reasonable clock skew.nonce(if you sent one in the authorization request): matches the value stored in the session.
sub in the ID Token is a stable user identifier for your app (team). Use it as the primary key when you associate a local user.
In the current implementation, email and most profile claims come from UserInfo. Do not assume the ID Token always contains email or picture. Exception: when locale is granted, the ID Token includes locale (BCP 47, such as zh-CN / zh-TW / en / ja), so the authorization callback can read the user’s preferred language. After you validate sub on the ID Token, call UserInfo with the Access Token for the rest.
Pairwise sub
For clients bound to a developer team, Swaymoon Account uses pairwise subject identifiers:
- The same natural person signing in repeatedly to apps under your team gets the same
sub. - The same person authorizing an app under another team gets a different
sub. - Therefore do not assume you can correlate users across apps or teams via
sub, and do not use email instead ofsubas the unique primary key — users can change their bound email.
UserInfo
GET /userinfo
Host: api-passport.swaymoon.com
Authorization: Bearer ACCESS_TOKEN
The endpoint returns JSON claims. sub matches the ID Token (pairwise).
Always present
| Claim | Meaning |
|---|---|
sub | Pairwise user identifier (as long as openid is present) |
Profile claims: per-field authorization + defaults
The following claims always appear in the UserInfo response (stable field shape). Whether they are real user data depends on whether the token scope includes the matching field (see Scopes and consent).
| Claim | Required scope | When granted | When not granted (default) |
|---|---|---|---|
name | name | Display name | Swaymoon User |
nickname | nickname | Nickname; Swaymoon User if unset | Swaymoon User |
picture | picture | Avatar HTTPS URL; empty string if unset | "" |
biography | biography | Bio; empty string if unset | "" |
gender | gender | male / female / other; empty string if unset | "" |
birthdate | birthdate | YYYY-MM-DD; empty string if unset | "" |
region | region | Region code; empty string if unset | "" |
preferred_username | preferred_username | Username; empty string if unset | "" |
locale | locale | BCP 47 tag (zh-CN / zh-TW / en / ja) | "" |
updated_at | Any profile field | Profile update time (Unix seconds) | 0 |
Do not treat defaults as real profile data. Use scope on the token to see which fields were granted. Profile items not in scope are placeholders only.
Older tokens that still contain the bundled profile scope are treated as if all of the profile fields above were granted.
picture (avatar)
When picture is granted and the user has an avatar, UserInfo returns an absolute HTTPS URL (Issuer domain, for example https://api-passport.swaymoon.com/media/avatars/…). You can fetch it from the browser or your server with no extra auth. If no avatar is set, the value is an empty string.
When you persist it, choose one (or combine as needed):
- Store the URL only: write the
pictureURL into the local profile and display Swaymoon Account’s address. No object storage of your own. Simple. If the user later changes the avatar, the old URL may stop working (the filename includes a random segment). Update the stored link on sign-in / profile refresh, or read UserInfo again before display. - Store the file: GET the HTTPS URL from your server (or a safe client), download the image into your storage, and keep only your resource URL locally. Use this if you want long-term hosting on your CDN, or offline / crop processing. Cache reasonably and do not fetch in a tight loop.
Both approaches assume a production Issuer over HTTPS. If local testing uses http://…, the returned URL may also be HTTP. Verify against production config before launch.
email
Returned only when the token includes email:
| Claim | Meaning |
|---|---|
email | Address you can use to contact the user (verified) |
email_verified | true (when an email is returned) |
If email is not granted, these claims are absent (unlike profile defaults).
On the consent page the user can grant email as a regular email or Hide My Email:
| User choice | email you receive | Real email |
|---|---|---|
| Regular email | The address bound to the account | That address |
| Hide My Email | A relay dedicated to your app (…@privaterelay.swaymoon.com) | Not provided via UserInfo or other OAuth channels. Mail to the relay is forwarded by Swaymoon Account to the real address |
In both cases email is a working inbox. Treat it as a normal email (register, send mail, display). The user may choose Hide My Email, so do not assume it is their everyday private address, and do not try to obtain or infer the real one. Keep using sub as the account primary key.
Email required to finish authorization: if the authorization request includes email and the user checks / is required to grant it, an account without a bound email is guided to bind and verify first, then continues to consent and code issuance. When the token has the email scope, UserInfo should always include email and email_verified. The user can also uncheck optional email and continue (if you did not make it required).
Revoke and introspect
- Revoke:
POST {ISSUER}/oauth2/revoke(pass the token per RFC 7009 and the server’s implementation). - Introspect:
POST {ISSUER}/oauth2/introspect(confidential clients can query token state).
Parameters follow the discovery document and the server response.
Account-creation tips
- Look up or create a local user with the validated
sub. - Interpret profile fields from the token
scope. Treat defaults on ungranted fields as “unavailable”; do not write them into the business profile. - Avatars: decide first whether to store the HTTPS URL only or download into your storage; see
pictureabove. - Confidential clients must keep
client_secretsafe; public clients have no secret. Do not collect or store the user’s Swaymoon Account password. - Access Tokens are short-lived; refresh as needed. When the user signs out, revoke the Refresh Token if your product requires it.
Next: read Scopes and consent. If you hit issues, see Examples and troubleshooting.