I IntelliAuth Docs

Every access token IntelliAuth issues carries two pieces of authorisation information: the audience (the API the token is for) and the scope (the permissions the token grants). Backends should verify both before serving a request.

Audience — who the token is for

The audience claim (aud) names the API the token was minted for. When your backend validates a token, it checks that the audience matches what this API expects. If a token issued for api.banking.cymmetri.com lands on a request to api.health.cymmetri.com, the validation rejects it — even though the token is otherwise perfectly valid.

jsonc
// JWT payload (decoded)
{
  "iss": "https://banking-cymmetri.intelliauth.local",
  "sub": "usr_01HZX...",
  "aud": "api.banking.cymmetri.com",   // ← this token is for this API only
  "scope": "openid profile read:transactions",
  "exp": 1750000000,
  "iat": 1749996400
}

The audience is set when the client requests a token. The IntelliAuth React SDK's getAccessToken({ audience: 'api.banking.cymmetri.com' }) mints a token for that audience. If your app talks to multiple APIs (your own + a third-party), you request a separate access token per audience — the SDK caches each one independently.

Scope — what the token permits

The scope claim names the permissions the token grants. It's a space-separated string of scope identifiers. Standard OIDC scopes:

  • openid — required for OIDC flows; grants identity claims.

  • profile — name, picture, locale, etc.

  • email — email + email_verified.

Plus any custom scopes your tenant defines for your API:

  • read:transactions — read-only access to transaction data.

  • write:settings — write access to user settings.

  • manage:applications — register and rotate applications.

When your backend gets a request, it checks two things:

  1. Audience matches.

  2. The scope claim contains every scope the requested action needs.

ts
function requireScope(scope: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    const tokenScopes = (req.user.scope as string).split(' ')
    if (!tokenScopes.includes(scope)) {
      return res.status(403).json({ error: 'insufficient_scope', required: scope })
    }
    next()
  }
}

app.get('/transactions', requireScope('read:transactions'), handler)
app.post('/settings', requireScope('write:settings'), handler)

Why per-app scope allowlists matter

Each application can declare which scopes it's allowed to request. This is a hard cap — even if a client asks for manage:applications, if its application registration doesn't include that scope in its allowlist, the platform refuses to mint a token with it. Set the allowlist when you register the application in the tenant admin console.

The right default: applications get the minimum scopes their feature set needs. Adding a scope to an application is a deliberate action by the tenant admin; the platform's audit log records who added it and when. This is how principle of least privilege gets enforced.

When a user signs in to an application that's requesting scopes for the first time (or scopes that haven't been previously consented to), they see a consent screen describing what permissions the application wants. They click Allow; the scopes are added to their consent record for that application; tokens then start including those scopes.

You don't have to write the consent screen — IntelliAuth renders it from the scope metadata in your tenant config. You do have to author the scope descriptions: each scope on the tenant has a human-readable name + description shown to end-users at consent time.

Scope strings vs. enforcement points

A common source of confusion: scope strings are claims in the token. Enforcing them is the backend's job. The platform mints the token with whatever scopes are validly requested; whether your /transactions endpoint actually checks for read:transactions is up to you.

The SDK helpers (requireScope above, equivalents in other SDKs) make this easy. The discipline is to actually use them everywhere — a token validly verified against the wrong scope is still authorised, which defeats the point.

See also