Demonstrate delegation
This page demonstrates token exchange with delegation. The example exchanges an access token for an access token with reduced scopes. You can also exchange ID tokens with delegation.
Prepare the demonstration
Start by preparing the demonstration:
May act script
The script adds a may_act
claim to the subject token.
-
In the Advanced Identity Cloud admin UI, select Scripts > Auth Scripts > + New Script.
-
In the New Script window, select OAuth2 May Act and continue.
-
In the new script window, name the script
May act
and save the following JavaScript:(function () { var frJava = JavaImporter( org.forgerock.json.JsonValue ); var mayAct = frJava.JsonValue.json(frJava.JsonValue.object()) mayAct.put('client_id', 'delegateClient') mayAct.put('sub', 'delegateClient') token.setMayAct(mayAct) }());
This script generates a
may_act
claim to permit the delegate actor client to exchange the subject token, provided it also supplies an actor token where it is the subject.
Subject client
The OAuth 2.0 client profile in this example overrides the OAuth 2.0 provider settings. This lets you test the script without affecting access tokens issued to other clients.
-
Create a confidential OAuth 2.0 client account to get an original token for the subject.
In the Advanced Identity Cloud admin UI, select Applications > + Add Application, and create a new Web client with the following settings:
- Client ID
-
myClient
- Client Secret
-
mySecret
-
Add the following settings under Sign On > General Settings and save your work:
- Sign-in URLs
-
https://www.example.com:443/callback
- Scopes
-
change_contract
repair
-
Override OAuth 2.0 provider settings for this client.
Under Native Consoles > Access Management, select Realms > alpha > Applications > OAuth 2.0 > Clients > myClient. Switch to the OAuth2 Provider Overrides tab, update the following settings and save your work:
- Enable OAuth2 Provider Overrides
-
Enabled
- OAuth2 Access Token May Act Script
-
May act
- OIDC ID Token May Act Script
-
May act
Actor client
-
Create a confidential OAuth 2.0 client account for the service that acts on behalf of the user.
In the Advanced Identity Cloud admin UI, select Applications > + Add Application, and create a new Service client with the following settings:
- Client ID
-
delegateClient
- Client Secret
-
mySecret
-
Add the following settings under Sign On > General Settings and save your work:
- Grant Types
-
Refresh Token
Token Exchange
- Scopes
-
repair
Test the demonstration
After preparing the demonstration, test your work using HTTP calls to REST endpoints.
The demonstration uses the Authorization code grant and and Client credentials grant flows followed by token exchange:
-
The resource owner authenticates to obtain an SSO token.
-
The subject client relies on Implied Consent being enabled (default) in the Advanced Identity Cloud admin UI under Applications > Client ID > Sign On > Advanced settings > Authentication. It assumes the resource owner grants the client access.
-
The subject client requests the authorization code and exchanges it for an access token. Your script sets the
may_act
claim in the access token. -
The actor client requests an actor token with its client credentials.
-
The actor client exchanges the subject token and actor token for an ID token.
Follow these steps:
-
Authenticate as the resource owner:
curl \ --request POST \ --header 'Content-Type: application/json' \ --header 'X-OpenAM-Username: <resource-owner-username>' \ --header 'X-OpenAM-Password: <resource-owner-password>' \ --header 'Accept-API-Version: resource=2.0, protocol=1.0' \ 'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/authenticate' {"tokenId":"<resource-owner-tokenId>","successUrl":"/enduser/?realm=/alpha","realm":"/alpha"}
-
Request the authorization code as the subject client:
curl \ --dump-header - \ --request POST \ --cookie '<session-cookie-name>=<resource-owner-tokenId>' \ --data 'scope=change_contract repair' \ --data 'response_type=code' \ --data 'client_id=myClient' \ --data 'csrf=<resource-owner-tokenId>' \ --data 'redirect_uri=https://www.example.com:443/callback' \ --data 'state=abc123' \ --data 'decision=allow' \ 'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/authorize' ... location: https://www.example.com:443/callback?code=<authorization-code>&iss=https%3A%2F%2F... ...
If you’re exchanging an ID token instead of an access token, set scope=openid profile
here to return the ID token in the next command. -
Exchange the authorization code for an access token or ID token as the subject client:
curl \ --request POST \ --user 'myClient:mySecret' \ --data 'grant_type=authorization_code' \ --data 'code=<authorization-code>' \ --data 'redirect_uri=https://www.example.com:443/callback' \ 'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/access_token' { "access_token": "<subject-access-token>", "refresh_token": "<refresh-token>", "scope": "change_contract repair", "token_type": "Bearer", "expires_in": 3599 }
Your script has set the
may_act
claim, which is not directly visible. To see themay_act
claim, you must introspect the access token. -
Request an access token as the actor client:
curl \ --request POST \ --user 'delegateClient:mySecret' \ --data 'grant_type=client_credentials' \ --data 'scope=repair' \ 'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/access_token' {"access_token":"<actor-access-token>","scope":"repair","token_type":"Bearer","expires_in":3599}
-
Request an exchanged token as the actor client:
curl \ --request POST \ --user 'delegateClient:mySecret' \ --data 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \ --data 'scope=repair' \ --data 'subject_token=<subject-access-token>' \ --data 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \ --data 'actor_token=<actor-access-token>' \ --data 'actor_token_type=urn:ietf:params:oauth:token-type:access_token' \ --data 'requested_token_type=urn:ietf:params:oauth:token-type:id_token' \ 'https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/access_token' { "access_token": "<exchanged-id-token>", "refresh_token": "<new-refresh-token>," "issued_token_type": "urn:ietf:params:oauth:token-type:id_token", "scope": "repair", "token_type": "Bearer", "expires_in": 3599 }
The
issued_token_type
shows this is an exchanged token.If your subject token is an ID token rather than an access token, set subject_token_type=urn:ietf:params:oauth:token-type:id_token
and provide the ID token as the value ofsubject_token
.