Authorization
How SenseCrypt decides who may sign in (the default-closed group-to-app access gate) and what end users may do downstream (resource servers, permissions, and roles that shape the permissions claim).
Authorization in SenseCrypt answers two distinct questions, and keeping them separate is the key to understanding the model:
- Who is allowed to sign in to an application? — the group-to-app access gate.
- What may an end user do in your downstream API? — RBAC: resource servers, permissions, and roles that shape the
permissionsclaim.
A third system controls which profile data ends up in tokens (scopes and typed attribute release), covered at the end. These three are independent: passing the access gate says nothing about what permissions you get, and having a permission says nothing about which profile claims are released.
The three systems and how they combine for one sign-in:
This page is about your end users in your applications. Administering SenseCrypt itself — and machine access to its Management API — uses a separate capability model (console roles and M2M roles). Don't conflate the two: the Role that grants an end user read:invoices and the console role that lets an operator create OIDC apps are unrelated. See Machine-to-machine for the M2M/console side.
Access: default-closed group-to-app
Each application — an OIDC client or a SAML SP — has a set of attached groups. A user may sign in to that application only if they are a member of at least one attached group. This gate is default-closed:
An application with zero attached groups admits nobody.
So the very first step in enabling sign-in for a new app is: create or pick a group, attach it to the app, and put users in it. An app with no attached groups is not "open to everyone" — it is closed to everyone. This is deliberate: a misconfigured or half-provisioned app fails safe.
The gate is enforced everywhere, on every mint
The access gate is not a one-time check at the front door. It is re-evaluated at every stage of the ceremony and on every token issuance:
- at enrollment (when a device registers against the app),
- when the phone fetches the sign-in payload (so a stale device errors before the face capture, not after),
- at the token exchange (the canonical enforcement point for OIDC), and
- on every refresh (see Tokens & sessions).
The practical consequence: removing a user from an app's groups, suspending them, deleting them, or un-enrolling them causes them to fail closed on their next token or refresh — not at token expiry. For SAML SPs the same model applies (an SP's attached groups are its sign-in gate, default-closed exactly like an OIDC app's).
Rejections are opaque
Gate failures are deliberately indistinguishable from one another and from other failures. At the token endpoint every failure — unknown client, wrong user, user not in a group, suspended user — collapses to the same invalid_grant. During the ceremony they collapse to a single opaque unauthorized. There is no "you're not in the right group" oracle to probe. This is part of SenseCrypt's anti-enumeration design and is intentional; do not expect a descriptive error that tells an attacker why access was refused.
Downstream RBAC: what end users may do
When your API needs fine-grained authorization, SenseCrypt can compute and emit a permissions claim for it. The model is Auth0-style, and every object is per-tenant:
- Resource server — represents one of your APIs, identified by an immutable audience string (for example
https://api.acme.com). It carries anenforce_rbacflag that decides whether permissions are emitted at all. - Permission — a single action string scoped to one resource server (for example
read:invoices). Permission strings are lowercase, colon-separated segments, with*allowed only as a whole trailing segment (read:invoices,write:billing:*). - Role — a named bundle of permissions. A role may span multiple resource servers, but a token only ever carries the permissions for the single API it targets.
- Assignment — a role reaches a user directly or transitively through group membership. Both paths are unioned.
Requesting an API audience
By default, an access token's audience is your own client_id and it carries no permissions. To get a token for one of your APIs, your app requests that resource server's audience via ?audience= (the Auth0 spelling) or ?resource= (RFC 8707). If both are supplied, resource wins.
Two gates apply, and both fail closed:
- Your app must hold a grant to request that audience at all. An unknown API and an ungranted API are indistinguishable — both fail closed with an opaque error (
invalid_targetbefore the token endpoint,invalid_grantat it). - The token endpoint is the canonical enforcement point. The audience rides through
/authorizeas a query parameter (which is forgeable), so it is re-validated at the exchange. Passing an audience through/authorizenever grants anything on its own.
What the access token carries
The shape of the access token depends entirely on the audience request and the resource server's enforce_rbac flag:
| Request | aud | azp | permissions |
|---|---|---|---|
| No audience (default) | your client_id | absent | absent |
Audience, enforce_rbac = false | the API audience | your client_id | absent (pure audience binding) |
Audience, enforce_rbac = true | the API audience | your client_id | the user's effective permissions (possibly []) |
So the permissions claim is gated on the resource server's enforce_rbac flag — it is never emitted for an RS that does not enforce RBAC, even if the user has roles. An empty permissions: [] (a user who passed the access gate but holds no matching permissions) is distinct from an absent claim.
How your API consumes it
Your API validates the token exactly as any resource server would:
- Verify the JWT signature against the tenant's JWKS (ES256), selecting the key by
kid. - Check
audequals its own audience string. - Authorize each request against the
permissionsarray (and/or check the token via the introspection endpoint).
SenseCrypt never enforces these permissions itself — it only emits them. Enforcement is entirely your API's job. Permissions are recomputed on every mint, including refresh, so revoking a role or removing a group member propagates to the next access token rather than waiting for expiry.
Two different permissions claims exist. The one described here rides in an end-user token whose aud is your API's audience (axis 2). A separate permissions claim rides in M2M tokens whose aud is the Management API and whose values are console capabilities (axis 1). Same claim name, different vocabulary, different audience, different verifier. See Machine-to-machine.
Scopes and attribute release
The third, independent system controls which profile claims appear in tokens. It is driven by scopes and a typed attribute schema, both per-tenant:
- Standard OIDC scopes
profile,email,phone, andaddressbehave as in OIDC. You can also define custom scopes. The protocol scopesopenidandoffline_accessare always available. - A claim is released only when a granted (OIDC) or SP-attached (SAML) scope releases it and the user actually has a value for it. No scope, or no value, means the claim is simply absent — there is no backfill.
- Attributes are typed (string, integer, decimal, boolean, date, choice, phone number, address, timezone, locale, and more) and validated at write time. Tightening a constraint never retro-invalidates already-stored values.
Release is checked at every layer that emits claims — PAR, /authorize, token mint, UserInfo, and the SAML assertion — so a scope your app was never granted can never leak its claims.
The SAML twist
For SAML, the SP's attached scopes are the release gate, and the SP's attribute map only renames already-released claims onto your SAML attribute names. The map can never release an attribute that no attached scope allows — a mapping entry for an unreleased claim is inert, silently dropped, not an error. See SAML for the attribute-release and NameFormat details.
Reserved claims are protected
Protocol claims (sub, iss, aud, exp, iat, jti, nonce, azp, scope, permissions, and the rest) cannot be injected via a custom attribute. A tenant admin cannot define a custom attribute named sub and have it overwrite the server-minted claim — reserved keys are rejected, and the token builders strip them at the sink. You can trust that the protocol claims in a token were set by SenseCrypt, not by tenant configuration.
Related
- Tokens & sessions — the full access-token shape, the
permissionsclaim, and refresh behavior. - OIDC & OAuth 2.0 — how the audience request rides through the flow.
- SAML — SP-attached scopes and the attribute map.
- Machine-to-machine — the separate console/M2M capability model.
Tokens & sessions
ID tokens, access tokens, and refresh tokens issued by SenseCrypt — ES256 signing, the sub claim, scopes, audiences, permissions, refresh-token rotation and families, and logout.
Integrations
Task-oriented guides for connecting your application to SenseCrypt — OIDC login, SAML SSO, SCIM provisioning, machine-to-machine, and CIBA.