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 issued | Subject | Acting client |
|---|---|---|
client_credentials | None | The client |
authorization_code, and refreshes of it | The user who signed in | The client |
| Token exchange | The subject token's subject, if it had one | The exchanging client, with the actor in act |
| CIBA | The person who approved | The client |
| A SPIFFE JWT-SVID | None | The 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.
| Endpoint | What it does | Token scope |
|---|---|---|
/resource_registration/resource | A 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 /permission | Returns a permission ticket for a registered resource, with HTTP 201 | uma_protection or introspection |
/delegation | A resource owner grants another user access to a registered resource, with scopes and an expiry | delegation_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-ticketreturns HTTP 400unsupported_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
Send feedback about this page