Script TIA Plugin
The Script Token Issuance Authorizer executes a custom JavaScript procedure to decide, per scope, whether the scope should be issued, denied, require user consent, or be issued with a capped time-to-live.
The Script TIA runs a JavaScript procedure at token issuance time to produce per-scope decisions. This enables custom business rules, attribute-driven logic, and integration patterns that the other TIA plugins cannot express.
Use Cases#
The Script TIA enables custom token-issuance-authorization logic. Common use cases include:
- Custom Business Rules — implement organization-specific logic for scope issuance that requires custom algorithms or decision trees, such as scope denial based on combinations of subject attributes, time of day, and grant type.
- Attribute-Driven Decisions — authorize the issuance of scopes against attributes that are not part of the standard TIA context, for example custom claims resolved upstream or values read from a configured data source.
- External Authorization Delegation — delegate the decision to an external HTTP service whose contract does not match the AuthZen protocol expected by the AuthZen TIA.
- Conditional Consent and Time-to-Live — require user consent or cap a scope’s lifetime only under specific conditions (for example a stricter TTL for high-risk clients).
Getting Started#
To create a Script TIA, sign in to the Admin UI and navigate to Profiles → Token Service → Scopes → Token Authorization. Select + New Token Issuance Authorizer, give the TIA a unique identifier, and choose the Script type.
Configure the following settings:
- Script — the JavaScript procedure that returns per-scope decisions. The script is validated at configuration-commit time, so a syntactically invalid script is rejected before it can affect token issuance.
- HTTP Client — optional. Select an HTTP client facility if the script needs to call an external service to inform its decision.
Once the TIA is configured, assign it to one or more scopes in the Scopes section of the Token Profile.
Script Requirements#
The Script TIA requires a JavaScript procedure that:
- exports a top-level function named
resultthat receives the procedure context as its single argument; - returns either a result built with
context.newResultBuilder()or a plain JavaScript object mapping scope names to decisions (or arrays of decisions).
A scope can carry more than one decision at once, for example, the procedure below requires user consent for the
transfer_money scope and caps its lifetime at 5 minutes:
function result(context) {
return context.newResultBuilder()
.requireUserConsent("transfer_money")
.setTimeToLive("transfer_money", 300)
.build();
}
Multiple decisions per scope are resolved by the framework - Deny wins over any other decision, the smallest
SetScopeTimeToLive duration wins, and RequireUserConsent is applied if present at least once.
Scopes for which the procedure does not return a decision are treated as denied. This avoids silently issuing scopes when a script forgets to handle one.
Script Context#
The context argument passed to the result function exposes the full token-issuance request context.
Request Data#
| Property | Description |
|---|---|
context.scopeNames | The set of scope names being evaluated by this TIA invocation. |
context.scopeValues | The set of full ScopeValue objects, including any configured TTL per scope. |
context.grantType | The OAuth grant type, e.g. "authorization_code", "client_credentials" |
context.clientAuthenticationMethod | The method used to authenticate the client |
context.client | The OAuth client requesting the token. |
context.subjectAttributes | Subject attributes from the authenticated session. |
context.contextAttributes | Context attributes from the authenticated session. |
context.authenticationAttributes | Authentication attributes. |
context.existingDelegation | The existing delegation for the subject, or null when a new delegation is being created. |
context.request | The current HTTP request. |
context.phase | The token issuance authorization pass this invocation is for: "PRE_CLAIMS_RESOLUTION" (scope-bound or Global authorizer) or "POST_CLAIMS_RESOLUTION" (the profile’s post-claims-resolution authorizer). |
context.resolvedClaims | The resolved custom claim values, keyed by claim name. Only populated in the POST_CLAIMS_RESOLUTION phase; empty before claim values are resolved. System claims (iss, sub, exp, …) are not included. |
context.earlierAuthorizerAttributes | The custom attributes contributed by token issuance authorizers that already ran for this issuance, accumulated last-wins. Empty in the PRE_CLAIMS_RESOLUTION phase. |
Deciding on Resolved Claim Values#
When the Script TIA is designated as the profile’s
post-claims-resolution
authorizer , it is invoked after claim values have been resolved and can base its decisions on them.
The following procedure denies the admin scope unless the resolved department claim is IT:
function result(context) {
var builder = context.newResultBuilder();
var iter = context.getScopeNames().iterator();
while (iter.hasNext()) {
var scope = iter.next();
if (context.getPhase() === 'POST_CLAIMS_RESOLUTION'
&& scope === 'admin'
&& String(context.getResolvedClaims().get('department')) !== 'IT') {
builder.deny(scope);
} else {
builder.allow(scope);
}
}
return builder.build();
}
The same script instance may also be used as a scope-bound or Global authorizer; checking context.getPhase()
keeps the resolved-value logic confined to the post-claims-resolution pass, where the values are available.
Data Sources and Services#
| Property / Method | Description |
|---|---|
context.getAttributeDataSource(id) | Returns the attribute data source configured with the given id, or null. |
context.getBucket(id) | Returns the bucket data source configured with the given id, or null. |
context.getWebServiceClient() | Returns the configured HTTP client for calling external services, or null if no HTTP Client was configured on this TIA. |
Result Builder#
context.newResultBuilder() returns a fluent builder for assembling the result:
| Method | Description |
|---|---|
.allow(scopeName) | Add an Allow decision for the scope. |
.deny(scopeName) | Add a Deny decision for the scope. |
.requireUserConsent(scopeName) | Add a RequireUserConsent decision for the scope. |
.setTimeToLive(scopeName, seconds) | Add a SetScopeTimeToLive decision for the scope. |
.addAttribute(name, value) | Add a custom attribute, exposed to the token procedure via context.tokenIssuanceAuthorizerAttributes(). See Custom Attributes . |
.attributes(map) | Add all entries of a map as custom attributes. |
.build() | Finalize and return the result. |
Multiple decisions can be added for the same scope, the framework applies deny-wins, minimum-TTL, and consent semantics when resolving them.
The script authorizer is an allowlist: it is invoked with a set of scopes, and any scope the script does
not explicitly decide on is defaulted to Deny. This matters depending on the role the authorizer is
configured in, because that determines which scopes it is handed:
- As a scope-bound authorizer it is invoked only with the scope(s) it is bound to, so it naturally only affects those - other scopes are never handed to it and are left untouched.
- As the Global authorizer or the post-claims-resolution authorizer it is invoked with all still-allowed scopes. A script that only denies one scope will therefore also deny every other scope it was handed, because those are left without a decision.
So when the script is the Global or post-claims-resolution authorizer, it must explicitly allow every scope
it wants to keep, for example by iterating context.getScopeNames() and calling .allow(scope) for the ones
to retain and .deny(scope) for the ones to remove:
function result(context) {
var builder = context.newResultBuilder();
var iterator = context.getScopeNames().iterator();
while (iterator.hasNext()) {
var scope = iterator.next();
if (scope === "account") {
builder.deny(scope);
} else {
builder.allow(scope);
}
}
return builder.build();
}When the script is the profile’s post-claims-resolution authorizer, a RequireUserConsent decision prompts the
user only on flows that issue tokens from the authorize endpoint (implicit and hybrid); on the token endpoint,
where no user is present, it is treated as Deny. See
Restrict-Only Semantics .