Skip to main content

Token lifecycle

Why does a resource server have to ask PolicyArc about every token? Because the token is only a reference. Policy decides once when the token is issued and again each time the token is checked, and the second answer can differ from the first. It can be narrower, because a grant removed after issuance applies at the next check. It can also be wider, as the known issue in Check the scope and resource shows.

How it works​

Issue. A client asks POST /token. Policy decides whether to grant the request and which scopes on which resource types. The server then makes a random token, 32 characters by default, and stores it with the policy input and output. Nothing about the grant is inside the token string. Get tokens shows each grant.

Check. A resource server, or the managed gateway, sends the token to POST /introspect with the context of one call, such as the scope that call needs. Policy runs again with that context. A token granted notes:read came back active for a read and {"active": false, "inactive_reason": "insufficient_scope"} for a write. The answer can also carry obligations for the resource server to apply. Enforce a token at your API runs both checks.

Refresh. A refresh token gets a new access token. Policy runs again, so the new token reflects policy now, not at the first grant.

End. By default an access token lasts 3600 seconds unless policy sets another lifetime, and a refresh token lasts 7 days. Get tokens names the settings that change these. POST /revoke removes either one at once. After that, introspection answers {"active": false, "inactive_reason": "invalid_token"}. A token marked single use by policy works for one introspection only. See single-use tokens.

Why it works this way​

A JWT access token carries its decision with it, and a resource server trusts that decision until the token expires. PolicyArc keeps the decision on the server instead. The cost is one round trip to /introspect per call. The gain is that a revocation applies to the next check, and a policy or denylist change applies once the policy engine loads it, within seconds. Every check also writes an audit row.

Who a token speaks for​

How the token was issuedSubjectActing client
client_credentialsNoneThe client
authorization_code, and refreshes of itThe user who signed inThe client
Token exchangeThe subject token's subject, if it had oneThe exchanging client, with the actor in act
CIBAThe person who approvedThe client
A SPIFFE JWT-SVIDNoneThe workload, by its SPIFFE ID

A token can also be bound to the client's key with DPoP. Introspection then reports "token_type": "DPoP" and the key's thumbprint in cnf.jkt.

UMA resources and delegations​

The server implements part of User-Managed Access (UMA) 2.0. These parts exist, and each needs a token with the scope shown.

EndpointWhat it doesToken scope
/resource_registration/resourceA resource server registers, lists, reads, updates and deletes resources it protects. A resource has a name, a type and scopes.uma_protection or introspection
POST /permissionReturns a permission ticket for a registered resource, with HTTP 201uma_protection or introspection
/delegationA resource owner grants another user access to a registered resource, with scopes and an expirydelegation_management

A token with only the introspection scope is accepted at /resource_registration and /permission. On our test instance such a token registered a resource and got a permission ticket, each with HTTP 201.

A registered resource takes part in token decisions. When a user grant, such as an authorization code, a refresh or a token exchange, names the resource's ID in resource, the client's own entitlement no longer covers it. Policy grants it only through an unexpired delegation to that user. The server narrows the requested scopes to those the delegation lists. The client's entitlement must then list every one of those scopes on the resource's type. If it lacks any one, the resource is dropped from the token.

POST /delegation also accepts grantee_client_id, but token decisions look up delegations by user only. A delegation to a client has no effect.

These parts do not exist.

  • No grant redeems a permission ticket. grant_type=urn:ietf:params:oauth:grant-type:uma-ticket returns HTTP 400 unsupported_grant_type. The server stores tickets and never reads them.
  • There is no requesting party token and no claims gathering.

We ran the resource registration and permission calls, and the ticket grant refusal. We read the delegation path in the code. A new environment's resource registry has no type with the delegation_management scope, so add one before you use /delegation.

Next steps​