Documentation
Documentation
Step 3: Refresh Token → ID-JAG
Exchange the Refresh Token from Step 2 for an ID-JAG (Identity Assertion Authorization Grant), targeting a specific Authorization Server and resource. This is the second §4.5 exchange (TE ②), and it produces the same kind of ID-JAG the OIDC path does, carrying the SAML identity in a sub_id claim.
Request parameters
| Parameter | Required | Value |
|---|---|---|
grant_type | Yes | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Yes | The Refresh Token from Step 2 |
subject_token_type | Yes | urn:ietf:params:oauth:token-type:refresh_token |
requested_token_type | Yes | urn:ietf:params:oauth:token-type:id-jag |
audience | Yes | The Authorization Server URL. Becomes the ID-JAG's aud claim. |
resource | Yes | The Resource Server API URL. Becomes the resource claim. |
scope | Optional | Requested scopes (e.g., todos.read) |
client_id | Yes | Your IdP client ID |
client_secret | Yes | Your IdP client secret |
audience identifies who will validate the ID-JAG (the Authorization Server); resource identifies what will be accessed (your API). Both are required, and both must match a resource connection on your SAML app, or the request is rejected with invalid_target.
Implementation
Response
| Field | Description |
|---|---|
access_token | The ID-JAG token |
issued_token_type | Confirms this is an ID-JAG |
token_type | N_A (the ID-JAG is not a bearer token; it's an assertion for Step 4) |
expires_in | Token lifetime in seconds (5 minutes) |
scope | Granted scopes |
IdenX revokes the Refresh Token the moment it issues the ID-JAG, so a stolen Refresh Token can't be exchanged twice. Need another ID-JAG? Run Step 1 and Step 2 again to get a fresh Refresh Token.
Decoded ID-JAG example
sub_id: the SAML identity
Where an OIDC ID-JAG has a plain sub, the SAML ID-JAG adds a structured sub_id with format: "saml-nameid" (§3.2.1). It carries the SAML NameID and every qualifier that was present:
| Field | Always present | Meaning |
|---|---|---|
format | Yes | Always saml-nameid |
issuer | Yes | The SAML Issuer of the original assertion |
nameid | Yes | The NameID value |
nameid_format | No | The NameID Format, when present |
name_qualifier | No | The NameID NameQualifier, when present |
sp_name_qualifier | No | The NameID SPNameQualifier, when present |
sp_provided_id | No | The NameID SPProvidedID, when present |
An email claim is also included when the NameID format is emailAddress. Downstream, the Resource Server resolves the user from sub_id, not sub.
client_id is the resource client
The client_id claim is the resource client ({client_id}-at-{resource_id}, e.g. client_abc123-at-todo0). It is the identity your app presents to the Authorization Server in Step 4, distinct from the IdP client used in Steps 2 and 3.
Error handling
| Error | Cause | Fix |
|---|---|---|
invalid_grant | Refresh Token is unknown, expired, or already used | Re-run Steps 1–2 to mint a fresh Refresh Token |
invalid_target | audience + resource don't match a resource connection | Confirm the connection exists on your SAML app and the values match exactly |
invalid_scope | A requested scope isn't allowed for the connection | Remove the scope, or add it to the resource connection |
Steps 4 and 5: shared with the OIDC path
From here the SAML and OIDC paths converge. The ID-JAG you just minted is redeemed exactly like an OIDC one:
- Step 4, JWT Bearer Grant: present the ID-JAG to the Authorization Server (authenticating as the resource client) to receive a scoped access token.
- Step 5, API Call: call the Resource Server with the access token as a standard Bearer credential.
The Resource Server reads sub_id to resolve the SAML user, provisioning them on first access.
On this page