Custom Claim
Overview
LoginRadius supports enriching JWT access tokens and ID tokens with custom claims. These claims allow you to embed additional user or contextual metadata directly into tokens during OAuth 2.0, OpenID Connect (OIDC), and JWT login flows.
By injecting custom claims at authentication time, downstream applications-such as APIs, gateways, or microservices-can make authorization decisions without extra API calls, improving performance, scalability, and flexibility.
LoginRadius provides two methods for adding custom claims:
- JWT Template Mapping: Use the JSON template editor in the admin console (Applications > Add/Edit application under the Tokens tab or SSO Integrations > JWT Integration ) to map static values or dynamic user profile fields directly into token claims.
- Script-Based Injection: Use Identity Orchestration (IO) Scripts to write JavaScript logic that dynamically computes and injects claims, including external API calls and conditional rules.
Use Cases
-
Role-Based Access Control (RBAC): Embed user roles or group memberships directly into the token to drive fine-grained access control in downstream services.
-
Session Context: Include session-specific data (e.g., login time, device ID) to enforce policies like session expiration or device-based access.
-
Custom Authorization Logic: Derive and inject claims based on custom business rules using scripts, for example, marking VIP users or enforcing region-specific restrictions.
-
Reduced Latency for API Gateways: Allow API gateways and services to authorize requests without making extra calls to identity or profile APIs.
Key Features
-
Seamless Integration with OAuth/OIDC Flows Both methods are applied automatically during authentication, scripts via the orchestration workflow, and templates via the application's Tokens configuration.
-
Dynamic Claims Logic (Script-Based) Supports complex logic using JavaScript (Node.js), including conditional rules, external API calls, and custom computations.
-
JSON Template Editor Define custom claims via a JSON template in the admin console without writing scripts. Supports dynamic user profile values, static values, dot notation for nested fields, array indexing, fallback defaults, and string concatenation.
-
Claims Injected at Token Generation Time Custom claims are evaluated and included in the final stage of token generation, ensuring up-to-date, context-aware data. Custom claims only add keys to the payload. A key that collides with a claim LoginRadius issues itself is skipped; see Reserved claim names.
Configurations
Method 1: JWT Template Mapping
JWT Template Mapping defines custom claims from a JSON template in the Admin Console, with no scripting required. Placeholders resolve against the authenticated user's profile, so to read the profile's first name, you pass {{ FirstName }}. A claim value can also be a static string or number.
Follow these steps:
-
Open application settings:
- Navigate to Applications.
- Select an existing OIDC/OAuth application or create a new one.
- Open the application and go to Applications → Tokens.
-
Edit the JWT template:
- In the JWT Templates Mapping section, a text area displays a sample JSON template.
- Edit the JSON to define the claims you want to include. Use
{{ }}placeholders to insert profile values, or enter static string and number values directly.
The following is a sample template for reference:
{"email": "{{ Email.0.Value }}","full_name": "{{ FirstName }} {{ LastName }}","location": "{{ City }}, {{ State }}","country_code": "{{ Country.Code }}","country_name": "{{ Country.Name }}","account_type": "enterprise"} -
Validate and save:
- The Admin Console validates the template before saving. Malformed JSON, an unsupported operator, or an invalid path surfaces an inline error and blocks the save.
- Once the template is valid, save the application. Claims defined in the template are injected into issued Access Tokens and ID Tokens automatically.
For placeholder syntax, see Template Syntax. For claim names you cannot set and for size behavior at issuance, see Reserved claim names and Rendering limits.
Template Syntax
JWT templates use {{ }} placeholders to insert values from the authenticated user's profile. Paths resolve against the profile root, so a top-level profile field needs no prefix. Any claim can also take a static string or number with no placeholder at all.
Simple variable
Insert a top-level profile field:
{
"last_name": "{{ LastName }}"
}
Nested properties
Access nested fields with dot notation. Custom fields live under CustomFields:
{
"country_name": "{{ Country.Name }}",
"language": "{{ CustomFields.language }}"
}
Array access
Address array elements by 0-based index written as a path segment. Bracket notation such as Email[0].Value is rejected by the validator:
{
"email": "{{ Email.0.Value }}"
}
Fallback values
Use || to supply a default when the path is missing or resolves to null. Each placeholder accepts a single fallback, so chaining two or more || operators is rejected:
{
"name": "{{ LastName || 'Guest' }}"
}
The fallback operand is either a quoted literal or a second path. Literals take single quotes.
String concatenation
Combine multiple placeholders in a single string value:
{
"full_name": "{{ FirstName }} {{ LastName }}"
}
A placeholder that resolves to nothing renders as an empty string, so the preceding template yields " Smith" when FirstName is absent. Wrap optional parts in a fallback to avoid a leading or trailing space.
Object insertion
Insert an entire object or array by making the placeholder the whole claim value:
{
"custom_fields": "{{ CustomFields }}"
}
Objects and arrays resolve only in this position. Inside a concatenated string, the same placeholder renders as an empty string, because only strings, numbers, and booleans stringify.
Reserved claim names
Templates add claims; they never override the claims LoginRadius issues itself. A template key matching one of the following names is skipped and the issued value stands. The match is case-insensitive, so Exp is skipped along with exp.
iat, nbf, exp, jti, iss, aud, sub, acr, amr, azp, auth_time, nonce, at_hash, c_hash, sid, scp, clm, gty, custom, kid, typ, alg, jku, jwe, jwk
Rendering limits
The template is rendered once per token at issuance, and the result has to satisfy two constraints.
- 3072-byte cap: the rendered claim set is serialized and rejected if it exceeds 3072 bytes. On rejection, the template contributes no claims to that token, so keep large profile objects such as full custom-field maps out of the payload.
- Missing values are dropped: a placeholder with no value and no fallback removes its key from the token rather than emitting
null, and a nested object whose keys all drop out is removed as well. Array elements are the exception: an unresolved placeholder inside a JSON array stays in the token asnull, so give array elements a fallback.
Method 2: Script-Based Injection (IO Scripts)
Orchestration supports OIDC and OAuth 2.0, and with the IO scripts, you can add custom claims to JWT tokens dynamically. Follow these steps:
-
Create Application:
- Navigate to Applications.
- Create a new OIDC/OAuth application using the Add Apps button or select an existing application.
- Store the client ID, client secret, and application name.
-
Create a script to set custom claims:
- Go to Orchestration > Scripts
- Click on + New Script and set the script name.
- Create a script as per your requirements, set custom claims in access_token and/or idToken using
hook.accessToken.setCustomClaim()andhook.idToken.setCustomClaim(), and save.
The following is a script for your reference
const axios = require('axios');exports.execute = async (lrObject, hook) => {try {const response = await axios.get('https://blue-truth-6f59.mayankagrwal.workers.dev/');if (response){var role = response.data?.role;var permissions = response.data?.permissions;if (role) {hook.accessToken.setCustomClaim('role', role);hook.idToken.setCustomClaim('role', role);}if (permissions && permissions.length) {hook.accessToken.setCustomClaim('permissions', permissions.join(","));hook.idToken.setCustomClaim('permissions', permissions.join(","));}}} catch (error) {// Fail the flow with a custom error messagehook.setError('500', 'API Error', error.message);}};For more information on scripts, refer to this
-
Create IO Workflow:
-
Navigate to Orchestration > Workflow
-
Create a new workflow by clicking on New Workflow. Or select an existing one.
-
From the left panel, drag and drop the Scripts node under Helper Nodes, then place it after the Auth node.
-
Click the Script node, then select the script name set in step 1.
-
Once done, save and update the workflow
-
How it works
Both methods inject claims at the same point, the final stage of token generation. The difference is in how the claims are defined:
- Script-Based: The IO Script runs inside the orchestration workflow after authentication, computes claims dynamically, and calls
hook.accessToken.setCustomClaim()/hook.idToken.setCustomClaim(). - JWT Template Mapping: The template defined in the application's Tokens tab is evaluated at token issuance, placeholders are resolved against the authenticated user's profile, and the resulting claims are added to the token payload.
The authorization flow is the same for both methods:
-
Initiate login
-
Use the
/authorizeendpoint to start OIDC authorization. -
Provide workflow (required for script-based), client ID, and OIDC app name as shown in the following example.
https://<siteUrl>/service/oidc/<AppName>/authorize?client_id=<clientID>&redirect_uri=https%3A%2F%example.com&response_type=code&scope=openid%20email%20profile&state=<random>&workflow=<workflowName>&debugmode=false -
Once the user logs in, custom claims are injected into the token payload, either via the script or the template, depending on your configuration.
-
Exchange code for tokens
Use the
/tokenendpoint to exchange the code received from Step 1. For examplecurl --location 'https://<site url>/api/oidc/<OIDC App name>/token' \--header 'Content-Type: application/json' \--data '{"client_id":"<client ID>","client_secret":"<client secret>","redirect_uri":"http://localhost:3000","response_type":"token","grant_type":"authorization_code","code":"<code>"}'
The resulting access_token and id_token will include your custom claims. For more information, refer to this.
Best Practices
-
Handle Errors Gracefully (Script-Based): Use
hook.setErrorto handle validation or execution errors in IO Scripts. -
Always Validate Input (Script-Based): Ensure that fields like
lrObject.Identity.Emailexist before accessing them to avoid runtime errors. -
Use Fallback Values for Optional Fields (Template Mapping): Wrap fields that may be absent using the
|| 'default'syntax to prevent null or missing claims (e.g.,{{ CustomFields.region || 'global' }}). -
Validate JSON Before Saving (Template Mapping): Use the built-in admin console validator; malformed JSON will be rejected before it can affect token issuance.
-
Don't expose client secret: Always use the client secret in your backend server to avoid data breaches and security issues.