Synq Docs
Resources

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.

ClaimWhat it is
issThe brand's issuer URL.
subThe person's id at the brand (usr_…). It never changes, whatever they sign in with.
audThe app's client id.
iat, expWhen it was issued, and when it expires: an hour later.
auth_timeWhen the person last proved who they are, in seconds since the epoch. Always present.
acrHow 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).
amrHow they signed in (see below).
nonceThe nonce the app sent, which it checks.
sidThe person's session at the brand, as this app knows it. Back-channel logout tokens name the same session.
email, email_verifiedWith the email scope.
name, given_name, family_name, preferred_username, picture, localeWith the profile scope, when the person has them.
linked_accountsWith 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.

ClaimWhat it is
iss, sub, iat, expAs in the ID token. A machine app's own token has its client id as sub.
audThe API's identifier: an API refuses a token for any other.
azpThe app the token was issued to.
scopeThe scopes granted for this API, space-separated.
auth_time, acr, amrFrom the sign-in behind the token, when a person signed in.
actFor an agent acting for the person (the device flow): { "type": "agent", "client_id": "…" }. The API knows both who and what.
sidOnly on a token for the brand's account API: which of the person's sessions the call comes from.
email, email_verified, linked_accountsOnly 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
    }
  ]
}
FieldWhat it is
providergithub, google, microsoft, apple, facebook, x, discord, telegram or matrica.
subjectThe 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.
usernameTheir handle there as of their last sign-in with it, or null. It changes: never key on it.
details_read_atWhen Synq last read the account from the provider: their last sign-in with it.
account_type, created_at, public_repos, followersGitHub only: User, Organization or Bot, when the account was made, its public repositories and its followers.
email_verified, workspace_domainGoogle only: Google's own word that it checked the address, and the Workspace domain (hd), null for a personal account.
testtrue 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

ValueThe person signed in with
pwdA password
otpA code or link sent by email
walletA Solana wallet's signature (Synq's own value; RFC 8176 has none)
hwkA passkey bound to one device
swkA synced passkey
userWith a passkey, that the authenticator verified the person (a PIN or biometric)
fedAn 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"].

On this page