Token Issuance Authorizers

Token Issuance Authorizers manages the release of scopes through a configurable collection of Token Issuance Authorizer (TIA) plugins that can be assigned to a scope. Various plugins are available that each test for certain conditions to be met, such that the TIA plugin can authorize the issuance of a scope with or without conditions upon issuing a new token.

Token Issuance Process#

When a request is made to issue a token, the scopes to be granted are determined based on what is requested, what is allowed by configuration, and with Token Issuance Authorizers, it is also based on what is authorized by the applicable TIA plugins. The following diagram illustrates the role of TIA plugins in the token issuance process.

Overview of Token Issuance process
Overview of Token Issuance process

The grant types that support Token Issuance Authorization are

  • Authorization Code Flow
  • Client Credentials Flow
  • Refresh Token Flow
  • Resource Owner Password Credentials Flow
  • Device Flow
  • CIBA
  • Token Exchange Flow
  • OAuth Token Exchange Flow
  • Implicit flow
  • JWT Assertion Flow
  • Assisted Token Flow
  • Pre-Authorization Flow

TIA Plugin Operation#

When a token is being issued, the scopes to be issued together with the context in which the request is made, are provided to the applicable TIA plugins for authorization. This results in an authorization decision for each scope. An authorization decision can be

  • Allow (the scope can be issued)
  • Deny (the scope cannot be issued)
  • Conditional

If a plugin authorizes the issuance of a scope with conditions, then those conditions must be met for the scope to be issued.

The conditions that the TIA plugin can return, are

  • Require User Consent
  • Set Scope Time To Live

The Require User Consent condition allows a TIA plugin to indicate that it is ok to issue the scope, but the user must first give their consent to the release of the scope. This is useful for cases where a scope is considered to be more sensitive and requires an explicit user action to allow it to be released in a token.

Be aware that requiring user consent can only be performed when the user is present, such as during an authorization code flow. If the user is not present, the scope may still be issued when a token is issued based on an existing delegation, but if the user is not present and a token is to be issued based on a new delegation, the scope will not be allowed.

For example, when a client requests an access token by starting the code flow. The user authenticates and the TIA plugins determine that the requested scope messages:read can be issued when user consent is given. The user is prompted for consent and issuance of the scope depends on the user decision.

Continuing the example, the access token expires and the client is using a refresh token to get a new access token. The TIA plugins determine (again) that for the requested scope messages:read, user consent is required. However, since the delegation already exists (implying that conditions for initial token issuance have been met), user consent was implicitly given at the time of the initial token issuance, and the new access token’s scope will include the messages:read scope without actual user-interactive consent.

Another example is when a client requests an access token by starting the JWT Assertion flow. A resulting token will be based on a new delegation. When the TIA plugins indicate that user consent is required for the requested scope messages:read, the scope will not be issued since the user can not be asked for consent to include the scope.

Condition: Set Scope Time To Live#

The Set Scope Time To Live condition allows a TIA plugin to indicate that it is ok to issue the scope, but it must be issued with the specified time to live value. The result is similar to how a lifetime on a scope can be configured , however in the case of it being the result of TIA plugin authorization, the lifetime is determined at the time of token issuance and can be dynamic based on the context of the request.

For example, a TIA plugin could set a shorter lifetime for a scope if the user authentication freshness has passed a certain age.

The openid Scope is Exempt#

The openid scope is protocol-critical: it is what makes a request an OpenID Connect request and it gates the issuance of the ID token. It is therefore exempt from token issuance authorization:

  • A Token Issuance Authorizer cannot be assigned to the openid scope — the configuration is rejected at commit time.
  • The Global and Post-Claims-Resolution authorizers receive openid in their input set like any other scope, but any restrictive decision (Deny, Set Scope Time To Live, Require User Consent) targeting it is ignored (each ignored decision is recorded with a debug log entry). The openid scope is always issued when it is requested and allowed.

This matters in particular for the Script TIA used as a Global or Post-Claims-Resolution authorizer: the script plugin denies every input scope its script returns no decision for, which would otherwise silently strip openid — and with it the ID token — from a script that only decides the scopes it knows about. A script does not need to (and cannot meaningfully) decide the openid scope.

Custom Attributes#

In addition to per-scope decisions, a TIA plugin can return custom attributes - arbitrary values it computed while making its decision, such as a risk score. These attributes are then readable by the token procedure that issues the token, which can use them to shape the issued tokens (for example, copying a value into a token claim).

A token procedure reads the attributes through the dedicated tokenIssuanceAuthorizerAttributes() accessor:

function result(context) {
    var risk = context.tokenIssuanceAuthorizerAttributes()["risk"];
}

The following points describe how these attributes behave:

  • Request-scoped, never persisted. The attributes live only on the procedure context for the current issuance. They are not written to the delegation or the stored grant. Because a TIA runs on every issuance (including on refresh), the procedure always sees freshly computed values — nothing stale can survive in the stored grant.
  • Kept separate from context attributes. They are exposed through their own accessor and are never merged into context.contextAttributes(), so a TIA can never shadow authenticator/protocol context attributes such as acr or auth_time.
  • Last-wins on conflict. When more than one TIA runs (the Global TIA plus scope-bound TIAs, or the children of the Composite TIA ) and they return the same attribute key, the last value written wins. Each override is recorded with a debug log entry.

Global Token Issuance Authorizer#

In addition to scope-bound TIAs, an OAuth profile can configure an optional Global Token Issuance Authorizer. The Global TIA runs once, before any scope-bound TIAs are evaluated, and receives the full set of requested-and-allowed scopes — including the empty default scope when no scopes were explicitly requested. This gives the Global TIA a say in every scope that may be issued, plus the ability to authorize (or refuse) the default-scope path.

Decisions made by the Global TIA fold into the same Allow / Deny / Set Scope Time To Live / Require User Consent semantics as scope-bound TIAs. A scope denied by the Global TIA is removed from the issuance set before scope-bound TIAs are consulted, so scope-bound TIAs are never invoked for scopes the Global TIA has already denied. The collapse rules (Deny wins, smallest TTL wins, Require User Consent accumulates) apply across the Global and scope-bound passes.

Configuration#

The Global TIA is an optional leaf inside the token-issuance-authorizers configuration that references one of the TIAs defined in the same container by id:

<token-issuance-authorizers xmlns="https://curity.se/ns/conf/profile/oauth/authorization-server">
    <global-token-issuance-authorizer>my-global-tia</global-token-issuance-authorizer>
    <token-issuance-authorizer>
        <id>my-global-tia</id>
        <!-- one of the provided TIA plugins, e.g. script, composite, authzen, ... -->
    </token-issuance-authorizer>
</token-issuance-authorizers>

The referenced id must match an existing token-issuance-authorizer entry; this is enforced as a referential integrity constraint at configuration time. The same TIA plugin instance can be used as both the Global TIA and a scope-bound TIA on individual scopes if desired.

The Global TIA can be set in the Admin UI by navigating to the Token Issuance Authorizers section of the Token Profile and selecting the Global Authorizer button on the top right. This opens a dialog where you can select one of the configured TIA plugins to be used as the Global TIA.

Global Token Issuance Authorizer on Scopes in Admin UI
Global Token Authorization. (Admin UI version: 11.3)

Default Scope Authorization#

When a client requests a token without supplying a scope parameter, an internal “default scope” (an empty-named scope) is used. Scope-bound TIAs cannot bind to the default scope, so before the Global TIA was introduced there was no way to authorize or deny the default-scope path through the TIA mechanism. The Global TIA closes that gap: it receives the empty default scope in its input set and can Allow or Deny it like any other scope.

Behavior when Everything is Denied#

When all TIAs (Global and scope-bound combined) deny every requested scope and no individually-requested claim survives, the token endpoint refuses the request with an OAuth access_denied error rather than issuing a token with empty scope and empty claims. This applies regardless of whether the denial came from the Global TIA, a scope-bound TIA, or both.

If individually-requested claims (via the OAuth claims request parameter) survive the scope denial — i.e. they are still allowed to be issued because they were not exclusively tied to a denied scope — the token is issued with those claims, even when no scopes survive.

Post-Claims-Resolution Token Issuance Authorizer#

The TIAs described so far run before claim values are resolved: they see which scopes and claims are requested, but not the values the claims will resolve to. An OAuth profile can additionally designate one Post-Claims-Resolution Token Issuance Authorizer - a TIA that runs once more, after claim values have been resolved - so it can authorize based on the actual resolved values. For example, it can deny the admin scope when the resolved department claim is not IT.

The designated authorizer runs wherever the affected token is issued, after the Global and scope-bound TIAs and consent filtering have produced the allowed set: at the token endpoint for the code, refresh, client credentials, ROPC, assertion, device and token-exchange grants, and at the authorize endpoint for the tokens that flow issues directly - the implicit flow and the front-channel token/id_token of the hybrid flow (a hybrid flow’s authorization code is separately governed by the pass at the token endpoint when the code is redeemed). Like the Global TIA, it is invoked once with all still-allowed scopes, regardless of scope bindings.

Restrict-Only Semantics#

The post-claims-resolution pass can only further restrict what the earlier passes allowed:

  • Deny removes a scope; the claims that scope carries are removed with it under the same rules as any TIA denial - a claim is kept when another still-allowed scope also carries it, or when it was individually requested via the claims request parameter.
  • Set Scope Time To Live composes with earlier TTL decisions; the smallest value wins.
  • Allow is a no-op - it cannot re-add anything an earlier pass denied.
  • Require User Consent behaves according to the endpoint. At the authorize endpoint (implicit and hybrid), where the user is present, it prompts for consent - folded into the same consent screen as the earlier passes, so the user is asked once - and the scope is kept only if consent is granted. At the token endpoint, where no user is present, it is treated as Deny.

A TIA plugin can detect which pass it is invoked in via the phase on its context, and read the resolved claim values and the attributes contributed by earlier authorizers. The same plugin instance may serve as a scope-bound, Global, and post-claims-resolution authorizer at once.

Claim resolution and the implicit / hybrid flows#

At the token endpoint the resolved claim values are computed once and reused, so there is no double resolution. On the implicit and hybrid flows the authorize request is re-executed after the consent screen (the whole request is replayed on return), and the post-claims-resolution authorizer needs resolved values before consent is gathered, so on those flows claim values may be resolved more than once for a single logical authorization.

This matters only if a claim’s resolution depends on the set of requested scopes (a claims provider or value procedure that inspects the scopes): such a claim can resolve to different values across the provisional and final resolutions, or between an interactive and a non-interactive issuance of the same grant. Claim value resolution should not depend on the scope set; if it must, normalise the value in the token procedure so the issued token is consistent regardless of the flow.

Configuration#

The designated authorizer is an optional leaf inside the token-issuance-authorizers configuration that references one of the TIAs defined in the same container by id.

Configuring a post-claims-resolution authorizer causes claim values to be resolved eagerly during token issuance on the profile. At the token endpoint the values are resolved once and reused, so there is no double resolution cost. On the implicit and hybrid flows the values may be resolved more than once per authorization (see Claim resolution and the implicit / hybrid flows). When the leaf is not configured there is no second pass and no eager resolution on any flow.

Delegations and authorization codes issued by versions before 11.4 do not record which claims were requested individually via the claims request parameter. When the post-claims-resolution authorizer denies a scope while a token is issued from such a delegation (for example on a refresh), a claim that was individually requested and is carried only by the denied scope is removed together with the scope, instead of being kept. Tokens are never issued with more than what was authorized — the effect is only ever additional restriction. A new authorization records the distinction and restores the documented behavior.

Configuring Token Issuance Authorizers#

Token Issuance Authorizer plugins are configured in the Token Issuance Authorizers section of the Token Profile. Each plugin has its own configuration options. A configured TIA plugin can be assigned to a scope to create configurable authorization logic to control the release of scopes when issuing tokens.

To create a new TIA plugin, navigate to the Token Authorization section of the Scopes menu and click the +New button.

Token Issuance Authorizers in Admin UI
Token Authorization. (Admin UI version: 11.2)

Assigning Token Issuance Authorizers To Scopes#

To assign a TIA plugin to a scope, navigate to the Scopes section of the Token Profile and click on the scope you want to edit to see Scope details in the right column. A Token Issuance Authorizers section appears where you can select one of the configured TIA plugins that will be applied when that scope is considered to be issued.

Token Issuance Authorizers on Scopes in Admin UI
Token Authorization. (Admin UI version: 11.2)

Provided TIA Plugins#

The following TIA plugins are provided out of the box:

Was this helpful?