Authenticate Client Application Using OAuth 2.0 Token Exchange On-Behalf-Of (Delegation) Flow
OAUTH
By Karmen Lei | Published: Aug 11 2022
OAuth 2.0 Token Exchange on-behalf-of (delegation) flow enables client applications to act on behalf of a different entity, for example, another client application or a user. We are here to show you how you can implement it to your system using the SecureAuth platform!
About Authenticating Client Apps Using OAuth 2.0 Token Exchange Delegation Flow
OAuth 2.0 provides the support of token exchange in the delegation or "on-behalf-of" scenario. A common use case for the delegation is to allow a resource (actor) to make calls to a backend service on behalf of the requesting user (subject). In practice, the resource needs an access token of the requesting user without direct user interaction. It can be accomplished by leveraging the JWT Bearer Flow or Client-Initiated Backchannel Authentication (CIBA) flow. If a trust is implicit and the authorization of the user can be assumed to be acted on, the JWT Bearer Flow can be used. In case we need an explicit trust, we need a higher security level, and we want to obtain an authorization from the end user, then we can use the CIBA flow to fetch authorization from the user from a different consumption device.
In this tutorial, we will show how the JWT Bearer Flow can be used to obtain a JSON Web Token (JWT) and used to obtain the subject access token from the SecureAuth platform. We will show how to obtain the actor access token via the SecureAuth Demo Application. Finally, we will show how to request a delegation access token by providing both the actor and subject tokens in the Token Exchange API call.
Token Exchange Support at SecureAuth
SecureAuth supports both types of the OAuth 2.0 Token Exchange: Impersonation and Delegation. If you wish to learn more about the impersonation flow, see the Token Exchange Authorization Basics article. Additionally, for the impersonation flow,SecureAuth's authorizers are capable of exchanging incoming third-party access tokens to internal access tokens and using them as the means of authenticating your request to access protected data.
The Postman collection is a quick way to illustrate the token exchange request and examine the request and response structures.
Prerequisites
Configure SecureAuth Trusted Service Application for JWT Bearer Flow
Note
SecureAuth supports using JWT profile for OAuth 2.0 authorization flows
Configure Workspace
Add a Demo Environment workspace or use an already existing one that has the Demo Application connected.
We are adding the Demo Application as it is later on needed to retrieve an access token in a convenient way.
Add a Sandbox IDP and a sample user that provides us with a possibility to test different scenarios involving users without a need of setting up any IDP integration.
Enable the JWT Bearer Flow and Token Exchange in your workspace settings.
Create and Configure Service Application
Within the workspace created and configured in the Configure Workspace section, create an application.
For the purpose of this article, we recommend creating a Service application.
In the OAuth tab of the client application settings, mark your application as trusted.
Enable the JWT Bearer Flow and the Token Exchange for your client application.
Prepare a JSON Web Key.
You can use the JSON Web Key generator for creating JWKS or any other tool able to generate a JWK.
Select the key size, algorithm, and key ID of your choice. Make sure that the
Key Use
(the purpose of the key) is set to signature.Copy the public/private keypair set (JWKS) and paste the public key into the JWKS Web Key Set configuration in your SecureAuth service application OAuth configuration.
Example JWKS:
{ "keys": [ { "p": "-khFUHsZVW_4II7zymeVNFxeaIz1bPFRXFi3XXYfKBMMrCP4kxKvGzJ1wGh3zL8r4qJhOD1wJIZ3ws7U7RZnfg0-klK3fCzrsnTHbH-9vAXBCB99i-OW9Q3CbiOgPrtukDXsZzT9rfQ7-LfGAHT_sw6uSL0Cci_3M-FxhBV0qe8", "kty": "RSA", "q": "mBVsPCAwZKvAwa7SpkNKKGo_z45LTSIuQCWqwJVAhdZO2rCUJDKkFTMiv0OEAYGakiIsOG_L73vzs5KQcip9L47ui-kIaeWOLJdhcOIe0F13Faauu2JOzOQoIoQpRA51qFWoA5koCjAovnynjiNX4NiuvxWZJ5aBpqyZ6hrW6jU", "d": "PDJaOJMVYvAyk8l2RAitz_fbrqjREIXNNgkkgkwBWu60dSWLNxrTH5Bwl9EZ6Rfqw1-Mb2e8j4Kaq7HMmjh6Fd3-jUBVnA3O1sejgnOgruhx-MShQ7Rgf1I5e755JDwguuukH--r1yx7i55_Qzfv9BpUOF-2Dnqciydx-MeNWBFBAF5IP47Z4QyFKPVwMjQwfPRXHDf_5CVIl_jw79lFpqCipeH5aWPS5zhNptkeXusnWSSYvZFFxXcoSkJpCryZsiZApFBeedMa_SfffUAa2eZomBP6NSAAdmPGsE4LmFJvzC-pcol72y2KSmNdsChbMLQazTqW_M9rh0ybwAbwuQ", "e": "AQAB", "use": "sig", "kid": "test", "qi": "Eoxomz8mrQhlHc9hgSS2i_FrOZwug1led0SJZqyQWjxNz2dh7fEETHeQHxRYqfClpUzR2kOxkDStwLT65svyJItKZuORV0_iHiUrl-1YKBESrpgWmEFMmBsPY9fuFItfQIQMG2XI3OqY4IakWehC6l9_b1hZMn56tKm8pRoCGg8", "dp": "puBjHvtE8_t5NLQ8Bruz9zqTVCCQT3hDaFBayn1E3uJ8s5Y0WlO932Nwsb62ChV5PF5KdkY4rJHf7kscG4yxqpFIJCX0413MNau5gDVYG7K2dyUP_6La9z-aYPduFAXC_XKG0nW5oy_SgM5_vbRE_W2PdT5ZECu9JqFtn8TyiY8", "alg": "RS256", "dq": "V4qdTs7IjGpA_jv_jSzWlwTxzit-NNwj-1CzRT-kcGW35jIWU-_Ce2F_8tCOP7-2lb-N7L6NiUC0Ov12oADNJn8D2Q0-hpZYjFpnAG9ohVAHqDLWCY7BBTef0p54knAwUcFcgRW78oFeMqfWWv8FKqwPinaIPj1oS-S2hAcHBrE", "n": "lK_a5r5Xrz07xX4iMF3giIVqB-WtNe7cqA7_sgvTVk4gZdQuPb_5xVpgssf9FovrTpu0E5ts1f4cVJnzvyxlIzA3h1ib9bqAdsakrdrsMnPYwx1v_eSoksIw0gZvMc60yCYyw_6vh_o8uk9vDz8QOCMLxKSCPEUmaGoasIWXqLG4-Z-b9xpPO3-EQGfAqJlmLzRjDAxO8TDKk6TOU9-hj4i2bj6Tn6VCxwRnnrITM1mJ0FA15AwSONOCDKHZ1ly8pkIe3bKNkdIdB_YU_yKz3mSIUOcSzmUXu3uF1yOE_fRQxMUg1UvOIKTfQ5x1XCpqyHPwzg68lyaazjAzaEWkew" } ] }
Prepare Postman Environment
Import the following environment setup to your Postman workspace:
{ "id": "b3c204f3-e16f-43f9-87aa-f1039117c0a5", "name": "CE Token-Exchange", "values": [ { "key": "url", "value": "", "type": "default", "enabled": true }, { "key": "workspaceId", "value": "token-exchange-delegation", "type": "default", "enabled": true }, { "key": "system_client_id", "value": "", "type": "default", "enabled": true }, { "key": "subject_token", "value": "", "type": "default", "enabled": true }, { "key": "actor_token", "value": "", "type": "default", "enabled": true }, { "key": "jwt", "value": "", "type": "default", "enabled": true }, { "key": "tenant_id", "value": "", "type": "default", "enabled": true }, { "key": "system_client_secret", "value": "", "type": "default", "enabled": true } ], "_postman_variable_scope": "environment", "_postman_exported_at": "2022-08-10T15:11:56.292Z", "_postman_exported_using": "Postman/9.25.2" }
Configure the following environment variables in Postman:
Env Variable
Description
Sample Value
url
Tenant host url
https://mytenant.us.connect.secureauth.com
tenant_id
Tenant identifier
mytenant
workspaceId
Name of the workspace you created
token-exchange-delegation
system_client_id
Client ID of your service app
48936d3522da48a89aae1b6ddb40a22f
system_client_secret
Client secret of your service app
4GocoroBIggcqKepSmKroVbwpZS-qpecGFGLVsjfpk8
Do not worry about the following variables yet, they will be populated later:
Env Variable
Description
Sample Value
subject_token
Access token of the requester
Automatically populated in Postman using JWT bearer flow
actor_token
Access token of the resource
Obtained from authenticating the user created in Sandbox IDP via the Demo app in the Get Actor Token section.
jwt
The JSON Web Token obtain for JWT Bearer flow
See the Get Token Using JWT Bearer Flow section.
Import the following collection to your Postman workspace:
Make sure your collection is imported to the same workspace you have your environment set up.
{ "info": { "_postman_id": "5f49a733-72c3-4ee8-9829-dc669395a471", "name": "Token exchange", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "Get access token from JWT", "event": [ { "listen": "test", "script": { "exec": [ "var jsonData = JSON.parse(responseBody);", "postman.setEnvironmentVariable(\"subject_token\", jsonData.access_token);" ], "type": "text/javascript" } } ], "protocolProfileBehavior": { "disabledSystemHeaders": { "content-type": true, "accept": true, "accept-encoding": true, "connection": true } }, "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/x-www-form-urlencoded", "type": "default" }, { "key": "Accept", "value": "application/json", "type": "default" } ], "body": { "mode": "raw", "raw": "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&client_id={{system_client_id}}&client_secret={{system_client_secret}}&assertion={{jwt}}", "options": { "raw": { "language": "text" } } }, "url": { "raw": "{{url}}/{{tenant_id}}/{{workspaceId}}/oauth2/token", "host": ["{{url}}"], "path": ["{{tenant_id}}", "{{workspaceId}}", "oauth2", "token"], "query": [ { "key": "subject_token", "value": "eyJhbGciOiJFUzI1NiIsImtpZCI6Ijk0NDM4OTMzOTA0ODQzODA3NTkwNzk0NzE5MjM5NDI3NjQ5NDM1IiwidHlwIjoiSldUIn0.eyJhY3IiOiIwIiwiYWlkIjoiZGVtbyIsImFtciI6WyJwd2QiXSwiYXVkIjpbImRlbW8tZGVtbyIsInNwaWZmZTovL21nbS1wb2MudXMuYXV0aHouY2xvdWRlbnRpdHkuaW8vbWdtLXBvYy9kZW1vL2RlbW8tdXNlci1wcml2YWN5LWNvbnNlbnQiLCJzcGlmZmU6Ly9tZ20tcG9jLnVzLmF1dGh6LmNsb3VkZW50aXR5LmlvL21nbS1wb2MvZGVtby9kZW1vLXByb2ZpbGUiLCJzcGlmZmU6Ly9tZ20tcG9jLnVzLmF1dGh6LmNsb3VkZW50aXR5LmlvL21nbS1wb2MvZGVtby9kZW1vLW9hdXRoMiJdLCJlbWFpbCI6ImZpcnN0LmN1c3RvbWVyQG1haWxpbmF0b3IuY29tIiwiZXhwIjoxNjU4NzcyMjg5LCJpYXQiOjE2NTg3Njg2ODgsImlkcCI6IjEyMWE3ODA5NDgxOTRhYzY4ZWZhYjFlNWY1YzlkZDAxIiwiaXNzIjoiaHR0cHM6Ly9tZ20tcG9jLnVzLmF1dGh6LmNsb3VkZW50aXR5LmlvL21nbS1wb2MvZGVtbyIsImp0aSI6ImExYmYyZDVmLTFlOGYtNDUyMy1iMWNmLTYzMWUzNjdmYmFiOSIsIm5hbWUiOiJGaXJzdCBDdXN0b21lciIsIm5iZiI6MTY1ODc2ODY4OCwic2NwIjpbImVtYWlsIiwiaW50cm9zcGVjdF90b2tlbnMiLCJtYW5hZ2VfY29uc2VudHMiLCJvZmZsaW5lX2FjY2VzcyIsIm9wZW5pZCIsInByb2ZpbGUiLCJ2aWV3X2NvbnNlbnRzIl0sInN0IjoicHVibGljIiwic3ViIjoiY3VzdG9tZXIxIiwidGlkIjoibWdtLXBvYyJ9.XTnZZA3hoL20XSa9KBVfEAPGYGDaVgmHp1Yiq6L7_Bg-o5sfJ8DYpLiglTdSW45oPUmctp6uj2ex-mDAv2QpcA", "disabled": true } ] } }, "response": [] }, { "name": "Token exchange", "protocolProfileBehavior": { "disabledSystemHeaders": { "content-type": true, "accept": true, "accept-encoding": true, "connection": true } }, "request": { "method": "POST", "header": [ { "key": "Content-Type", "value": "application/x-www-form-urlencoded", "type": "default" }, { "key": "Accept", "value": "application/json", "type": "default" } ], "body": { "mode": "raw", "raw": "grant_type=urn:ietf:params:oauth:grant-type:token-exchange&client_id={{system_client_id}}&client_secret={{system_client_secret}}&subject_token={{subject_token}}&subject_token_type=urn:ietf:params:oauth:token-type:access_token&actor_token={{actor_token}}&actor_token_type=urn:ietf:params:oauth:token-type:access_token&scope=profile", "options": { "raw": { "language": "text" } } }, "url": { "raw": "{{url}}/{{tenant_id}}/{{workspaceId}}/oauth2/token", "host": ["{{url}}"], "path": ["{{tenant_id}}", "{{workspaceId}}", "oauth2", "token"], "query": [ { "key": "subject_token", "value": "eyJhbGciOiJFUzI1NiIsImtpZCI6Ijk0NDM4OTMzOTA0ODQzODA3NTkwNzk0NzE5MjM5NDI3NjQ5NDM1IiwidHlwIjoiSldUIn0.eyJhY3IiOiIwIiwiYWlkIjoiZGVtbyIsImFtciI6WyJwd2QiXSwiYXVkIjpbImRlbW8tZGVtbyIsInNwaWZmZTovL21nbS1wb2MudXMuYXV0aHouY2xvdWRlbnRpdHkuaW8vbWdtLXBvYy9kZW1vL2RlbW8tdXNlci1wcml2YWN5LWNvbnNlbnQiLCJzcGlmZmU6Ly9tZ20tcG9jLnVzLmF1dGh6LmNsb3VkZW50aXR5LmlvL21nbS1wb2MvZGVtby9kZW1vLXByb2ZpbGUiLCJzcGlmZmU6Ly9tZ20tcG9jLnVzLmF1dGh6LmNsb3VkZW50aXR5LmlvL21nbS1wb2MvZGVtby9kZW1vLW9hdXRoMiJdLCJlbWFpbCI6ImZpcnN0LmN1c3RvbWVyQG1haWxpbmF0b3IuY29tIiwiZXhwIjoxNjU4NzcyMjg5LCJpYXQiOjE2NTg3Njg2ODgsImlkcCI6IjEyMWE3ODA5NDgxOTRhYzY4ZWZhYjFlNWY1YzlkZDAxIiwiaXNzIjoiaHR0cHM6Ly9tZ20tcG9jLnVzLmF1dGh6LmNsb3VkZW50aXR5LmlvL21nbS1wb2MvZGVtbyIsImp0aSI6ImExYmYyZDVmLTFlOGYtNDUyMy1iMWNmLTYzMWUzNjdmYmFiOSIsIm5hbWUiOiJGaXJzdCBDdXN0b21lciIsIm5iZiI6MTY1ODc2ODY4OCwic2NwIjpbImVtYWlsIiwiaW50cm9zcGVjdF90b2tlbnMiLCJtYW5hZ2VfY29uc2VudHMiLCJvZmZsaW5lX2FjY2VzcyIsIm9wZW5pZCIsInByb2ZpbGUiLCJ2aWV3X2NvbnNlbnRzIl0sInN0IjoicHVibGljIiwic3ViIjoiY3VzdG9tZXIxIiwidGlkIjoibWdtLXBvYyJ9.XTnZZA3hoL20XSa9KBVfEAPGYGDaVgmHp1Yiq6L7_Bg-o5sfJ8DYpLiglTdSW45oPUmctp6uj2ex-mDAv2QpcA", "disabled": true } ] } }, "response": [] } ] }
Get Tokens
You need to get both an actor and a subject token before exchanging them for a token using the on-behalf-of Token Exchange scenario.
Get Actor Token Using Demo Portal and Sandbox IDP
To get an actor token, you will use the Demo Application able to provide you with both decoded and encoded token.
Navigate to Dashboards view within the workspace.
Select Demo Portal in the Client Applications panel.
Authenticate using the sample user you created in the second step of the Configure Workspace section.
Select the Access Token tab and scroll down to the bottom of the view where a raw access token is visible.
Copy the raw access token and paste it as the value of the
actor_token
environment variable in your Postman environment setup.Note
If your access token becomes invalid, just re-login to the Demo Portal to get a new one.
Get Subject Token Using JWT Bearer Flow
Generate a JSON Web Token (JWT).
You can use any tool able to generate a valid JWT. For the purpose of this article, we will use the JWT Generator.
Sign the JWT with the public and private key pair you obtained in the fourth step of the Create and Configure Service Application section.
Provide all mandatory claims as a payload for the JWT and generate the JSON Web Token.
Example of a raw JSON Web Token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imh1R2UtbjVZWkQ3eFZOOV9RUWtJang1M2h0Z3hIRlREZGtIS2FpOU1QVlkifQ.eyJpc3MiOiJodHRwczovL3NhbXBsZS51cy5hdXRoei5zdGFnZS5jbG91ZGVudGl0eS5pby9zYW1wbGUvZGVmYXVsdCIsInN1YiI6InVzZXIiLCJhdWQiOiJodHRwczovL3NhbXBsZS51cy5hdXRoei5zdGFnZS5jbG91ZGVudGl0eS5pby9zYW1wbGUvZGVmYXVsdC9vYXV0aDIvdG9rZW4iLCJleHAiOjE2NjA5MTgzMDYsImlhdCI6MTY2MDc0NTMyNSwianRpIjoiNWI1N2Y4M2QtZTNjMC00OWRhLTgzMDAtNDAzNDBmNTU2MTQ3In0.XWTh9OfN10HfxmmsPpeiscTnHyr8Twm4c4UpbDD98FSWR-mdFmQgaukIVK3Ao-Nzuzvpr_mmxrWgzpKfsw93eMlSDINvmRwOwpfHAWFuvwx8giEgiSxka4_J4g4jVerob-2RazldbAZu_EsHYlCDo3ZXph_-pH8ggHtdHj9EdLbGyW7Yu6HDT5w-Ym-tXK6IU1yOCGMmtU6kDAsvlHkMxDbLMHQEgnRFbPNkt1pbRM2wBzDXcHTneimarDJxsLcYKS9GFSLrh7uO-gXkKybQ3eeuIk9-_04oDcpnmUOSiKmZ89GvXPFwbkFtX3p2J65PanQagPm_DOc4sd8D0vSOWw
In your Postman environment setup, add the generated JSON Web Token as the value for the
jwt
environment variable.Send a request to the Get access token from JWT API using Postman.
The Get access token from JWT API is a part of the collection you imported in the third step of the Prepare Postman Environment section.
Once the request reaches the SecureAuth platform, SecureAuth mints an access token and provides it in the request response. The provided access token is used to populate the
subject_token
environment variable and identifies the SecureAuth Trusted Service Application we created earlier.
Exchange Tokens In On-Behalf-Of Scenario
Now that you have both the actor token and the subject token ready, you can exchange them using the on-behalf-of (delegation) Token Exchange scenario.
To exchange the tokens, we will use the Token Exchange API from our Postman collection. The API:
Calls the SecureAuth OAuth 2.0 Token Endpoint with the
grant_type
parameter set tourn:ietf:params:oauth:grant-type:token-exchange
.Uses the environment variables you provided in the Postman Environment Setup, but, additionally, it includes the
subject_token_type
and theactor_token_type
parameters hardcoded tourn:ietf:params:oauth:token-type:access_token
.
By the presence of the subject_token
and the actor_token
parameters, SecureAuth can distinguish which mode of the Token Exchange is used, you do not need to worry about setting it yourself!
The Token Exchange API from our Postman collection, exchanges the actor and the subject token for a token minted by the SecureAuth platform using the on-behalf-of scenario for the Token Exchange. In our case, the Demo Application is the actor and is identified as the acting party to whom the authority is delegated. Our SecureAuth Trusted Service Application is the subject. It means that the Trusted Service Party Application is the party on behalf of whom the new token is requested.
Call the Token exchange API.
Examine the delegation token.
You can use a tool like JWT Debugger to examine the content of the exchanged token. It should include the actor claim
act
section to identify the resource (our Demo Application) which acts on-behalf-of the subject (our Trusted Service Application).Example of the decoded token:
{ "act": { "sub": "c37403831035079a4a814179c159ace600be7db8a07aac6212d03b9e54fd10f7" }, "aid": "{aid}", "amr": [], "aud": "247125f6bfc8464490fdf9b293266c41", "exp": 1660145566, "iat": 1660141966, "idp": "", "iss": "https://{tid}.connect.secureauth.com/{tid}/{aid}", "jti": "569d5e7a-1584-4ce7-9f32-10fa4032d6c7", "nbf": 1660141966, "scp": [], "st": "public", "sub": "da91ab1c90f947116e6b82aeaafe9fb5c3f97d874eff7fd629c68e52b1b3d5da", "tid": "{tid}" }
Adding Additional Claims to act Claim
When using the Token Exchange Delegation Flow, by default, the
act
claim of the exchanged token contains only thesub
claim that identifies the subject of the actor token. SecureAuth makes it possible to define additional claims to be included in theact
claim if needed.Additional claims can be added as part of the client application OAuth tab settings for a client that has the Token Exchange enabled.
Next Steps
Now that you have learned how to obtain a delegation access token using Token Exchange in SecureAuth. You can attach services with scope(s) to your service application and apply policies to govern whether a specific scope can be granted when the token is generated. See Restrict Access to Service and Configure Dynamic Scope.