Tokens and claims
Every claim in the ID tokens and access tokens a brand's issuer signs, and what each value means.
A brand's issuer signs two kinds of JWT. Check them with the issuer's keys from its discovery document (jwks_uri). @synqauth/js/server's createAccessTokenVerifier does it for an API. See Sessions and tokens for how long each lives and what ends them.
ID tokens
What an app learns about who signed in. An ID token is for the app alone (aud is its client id); never send one to an API.
| Claim | What it is |
|---|---|
iss | The brand's issuer URL. |
sub | The person's id at the brand (usr_…). It never changes, whatever they sign in with. |
aud | The app's client id. |
iat, exp | When it was issued, and when it expires: an hour later. |
auth_time | When the person last proved who they are, in seconds since the epoch. Always present. |
acr | How strong that sign-in was: urn:synq:loa:1 for one factor, urn:synq:loa:2 for two (a passkey that verified the person counts as two). |
amr | How they signed in (see below). |
nonce | The nonce the app sent, which it checks. |
sid | The person's session at the brand, as this app knows it. Back-channel logout tokens name the same session. |
email, email_verified | With the email scope. |
name, given_name, family_name, preferred_username, picture, locale | With the profile scope, when the person has them. |
linked_accounts | With the linked_accounts scope: the person's accounts at upstream providers (below). |
The claims parameter (claims_parameter_supported) adds no claim the scopes don't give. Its use is insisting on an acr: Insisting on a passkey.
Access tokens
What an API checks. An access token is asked for one API with resource=<identifier>. Without one, it is good for the issuer's userinfo alone. It is a JWT with typ at+jwt, signed ES256, and lives 5 to 15 minutes, as the API says.
| Claim | What it is |
|---|---|
iss, sub, iat, exp | As in the ID token. A machine app's own token has its client id as sub. |
aud | The API's identifier: an API refuses a token for any other. |
azp | The app the token was issued to. |
scope | The scopes granted for this API, space-separated. |
auth_time, acr, amr | From the sign-in behind the token, when a person signed in. |
act | For an agent acting for the person (the device flow): { "type": "agent", "client_id": "…" }. The API knows both who and what. |
sid | Only on a token for the brand's account API: which of the person's sessions the call comes from. |
email, email_verified, linked_accounts | Only for an API whose identity_claims name them, on a person's token (below). |
Linked accounts
linked_accounts lists the person's accounts at upstream providers, oldest first, one entry each, with what the provider said of the account at their last sign-in with it. An app gets it in the ID token and at userinfo with the linked_accounts scope. An API gets it in its access tokens and API key checks when its identity_claims name it (change an API), as it can get email and email_verified. An app that isn't the brand's own asks the person first, and the consent page names what the API learns.
{
"linked_accounts": [
{
"provider": "github",
"subject": "583231",
"username": "octocat",
"details_read_at": "2026-10-22T14:03:11.000Z",
"account_type": "User",
"created_at": "2011-01-25T18:44:36.000Z",
"public_repos": 8,
"followers": 21000
},
{
"provider": "google",
"subject": "109382745162839405172",
"username": null,
"details_read_at": "2026-10-20T09:12:40.000Z",
"email_verified": true,
"workspace_domain": null
}
]
}| Field | What it is |
|---|---|
provider | github, google, microsoft, apple, facebook, x, discord, telegram or matrica. |
subject | The provider's stable id for the account: GitHub's numeric id, which a rename keeps; Google's sub. Key anything you count per account on it. |
username | Their handle there as of their last sign-in with it, or null. It changes: never key on it. |
details_read_at | When Synq last read the account from the provider: their last sign-in with it. |
account_type, created_at, public_repos, followers | GitHub only: User, Organization or Bot, when the account was made, its public repositories and its followers. |
email_verified, workspace_domain | Google only: Google's own word that it checked the address, and the Workspace domain (hd), null for a personal account. |
test | true on a made-up account from a test sign-in, which only stages other than production offer. Absent otherwise. |
Use them to judge an account, as a free service does to keep out throwaway ones: a GitHub account at least a month old, of type User, with a public repository. Someone who links GitHub and Google to one account is one sub with two entries, so count per sub, and per subject too if you want two Synq accounts sharing a GitHub account to share a limit. @synqauth/js types them as LinkedAccount, on AccessTokenClaims and ApiKeyClaims alike.
amr values
| Value | The person signed in with |
|---|---|
pwd | A password |
otp | A code or link sent by email |
wallet | A Solana wallet's signature (Synq's own value; RFC 8176 has none) |
hwk | A passkey bound to one device |
swk | A synced passkey |
user | With a passkey, that the authenticator verified the person (a PIN or biometric) |
fed | An account at an upstream provider: Google, GitHub, Microsoft, Apple, Facebook, X or Discord |
A sign-in that adds a way to sign in carries both methods, such as ["pwd", "wallet"].