Skip to content

Commit 1e1ec90

Browse files
lrzhou25claudeavanscoy
authored
feat: add On-Behalf-Of Token Exchange API documentation (#1303)
* feat: add On-Behalf-Of Token Exchange API documentation Add comprehensive API documentation for the On-Behalf-Of (OBO) Token Exchange endpoint that enables middle-tier services to preserve user identity and permissions when calling downstream APIs. Changes: - Created new API documentation at /api/authentication/on-behalf-of-token-exchange/get-token - Documents the POST /oauth/token endpoint with grant_type token-exchange for OBO flows - Includes detailed parameter descriptions, request/response examples, and remarks - Updated navigation config for English, French (fr-ca), and Japanese (ja-jp) locales - Follows existing authentication API documentation structure and conventions The documentation covers: - RFC 8693 token exchange for preserving user identity in microservices - Actor claim delegation chain tracking - Custom API client prerequisites - Rate limits and caching best practices - RBAC-based scope filtering Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: improve OBO token exchange documentation clarity - Simplified description to focus on core functionality - Fixed typo: removed duplicate "to" in main description - Added more specific Custom API client requirements in remarks - Improved internal linking with proper relative URLs - Updated error response to reflect correct max delegation chain depth (5) - Enhanced parameter descriptions and documentation links Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Added obo token exchange french and japanese * Apply suggestions from code review Co-authored-by: Amanda VS <amanda.vanscoy@auth0.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Amanda VS <amanda.vanscoy@auth0.com>
1 parent dc47563 commit 1e1ec90

6 files changed

Lines changed: 369 additions & 0 deletions

File tree

main/config/navigation/api.authentication.en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@
106106
"group": "Custom Token Exchange",
107107
"pages": ["docs/api/authentication/custom-token-exchange/get-token"]
108108
},
109+
{
110+
"group": "On-Behalf-Of Token Exchange",
111+
"pages": ["docs/api/authentication/on-behalf-of-token-exchange/get-token"]
112+
},
109113
{
110114
"group": "Resource Owner Password Flow",
111115
"pages": [

main/config/navigation/api.authentication.fr-ca.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@
108108
"group": "Custom Token Exchange",
109109
"pages": ["docs/fr-ca/api/authentication/custom-token-exchange/get-token"]
110110
},
111+
{
112+
"group": "On-Behalf-Of Token Exchange",
113+
"pages": ["docs/api/authentication/on-behalf-of-token-exchange/get-token"]
114+
},
111115
{
112116
"group": "Resource Owner Password Flow",
113117
"pages": [

main/config/navigation/api.authentication.ja-jp.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@
108108
"group": "Custom Token Exchange",
109109
"pages": ["docs/ja-jp/api/authentication/custom-token-exchange/get-token"]
110110
},
111+
{
112+
"group": "On-Behalf-Of Token Exchange",
113+
"pages": ["docs/api/authentication/on-behalf-of-token-exchange/get-token"]
114+
},
111115
{
112116
"group": "Resource Owner Password Flow",
113117
"pages": [
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
title: "Get Token"
3+
description: "The On-Behalf-Of (OBO) Token Exchange enables a middle-tier service to exchange an incoming user token for a new token scoped to the downstream service."
4+
---
5+
6+
`POST /oauth/token`
7+
8+
The On-Behalf-Of (OBO) Token Exchange ([RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693)) enables middle-tier services to exchange a user-scoped token for a new token to call downstream services.
9+
10+
The new token maintains the original user's identity and permissions while being scoped specifically for the downstream service, enabling that service to make authorization decisions based on the end user. The delegation chain is tracked in the `act` (actor) claim, with each level representing a service in the call chain. To learn more, read the [On-Behalf-Of Token Exchange documentation](https://auth0.com/docs/secure/call-apis-on-users-behalf/on-behalf-of-token-exchange).
11+
12+
## Remarks
13+
14+
- Only Custom API clients associated with a resource server can use the OBO token exchange. A Custom API client has the following requirements:
15+
- Set `app_type` to `resource_server`.
16+
- Set `resource_server_identifier` to the valid resource server, i.e. `https://my-api.example.com`. Auth0 uses the resource server identifier as the audience parameter in authorization calls.
17+
18+
- The scopes issued to the application may differ from the scopes requested. In this case, a `scope` parameter will be included in the response JSON. Scopes are based on the user's [Role-Based Access Control (RBAC) policies](/docs/manage-users/access-control/rbac).
19+
20+
- OBO token exchanges trigger the [`post-login` Action trigger](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger), where `event.transaction.protocol` is set to `oauth2-token-exchange` and `event.transaction.actor` tracks the complete delegation chain.
21+
22+
- The delegation chain is limited to five nested levels. The OBO token exchange fails if the subject token already has five nested `act` levels.
23+
24+
- Cache access tokens for the lifetime of the token instead of requesting a new token for each API call. Access tokens are reusable until they expire; repeated token exchanges waste resources, increase latency, and may trigger rate limits.
25+
26+
## Parameters
27+
28+
<ParamField header="DPoP" type="string">
29+
A DPoP proof for the request. This is optional and only required if your application uses [Demonstrating Proof-of-Possession](/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop).
30+
</ParamField>
31+
32+
<ParamField header="auth0-forwarded-for" type="string">
33+
End-user IP as a string value. Set this if you want [Suspicious IP Throttling](/docs/secure/attack-protection/suspicious-ip-throttling) protection to work in server-side scenarios.
34+
</ParamField>
35+
36+
## Request Body
37+
38+
<ParamField body="grant_type" type="string" required>
39+
Denotes the flow you are using. For On-Behalf-Of Token Exchange, use `urn:ietf:params:oauth:grant-type:token-exchange`.
40+
</ParamField>
41+
42+
<ParamField body="subject_token_type" type="string" required>
43+
The type of the subject token. For On-Behalf-Of Token Exchange, use `urn:ietf:params:oauth:token-type:access_token`.
44+
</ParamField>
45+
46+
<ParamField body="subject_token" type="string" required>
47+
The incoming Auth0 access token from the user or upstream service that the middle-tier service is currently holding.
48+
</ParamField>
49+
50+
<ParamField body="requested_token_type" type="string" required>
51+
Indicates what kind of token you want back. For On-Behalf-Of Token Exchange, use `urn:ietf:params:oauth:token-type:access_token`.
52+
</ParamField>
53+
54+
<ParamField body="client_id" type="string" required>
55+
Your Custom API client's Client ID. The Custom API client must be associated with a resource server (same identifier). As for other grant types, you can also pass the Client ID in the Authorization header using HTTP Basic Auth.
56+
</ParamField>
57+
58+
<ParamField body="client_secret" type="string" required>
59+
Your Custom API client's Client Secret. As for other grant types, you can also pass the Client Secret in the Authorization header using `HTTP` Basic Auth. Review alternatives in [Auth0 Authentication API reference docs](https://auth0.com/docs/api/authentication#authentication-methods). Note that you cannot set `token_endpoint_auth_method` to `none` for OBO token exchange.
60+
</ParamField>
61+
62+
<ParamField body="audience" type="string" required>
63+
The unique identifier of the downstream API you want to access. This is the identifier for the downstream service that receives and validates the new token.
64+
</ParamField>
65+
66+
<ParamField body="scope" type="string">
67+
(Optional) A space-delimited list of specific permissions requested for the downstream call. If not specified, all scopes granted to the user for the target audience will be included based on RBAC policies.
68+
</ParamField>
69+
70+
## Response
71+
72+
| Status | Description |
73+
|--------|-------------|
74+
| 200 | Successful response. Returns an access token for the downstream API with the same user identity preserved. |
75+
| 400 | Bad request. May occur if the delegation chain depth exceeds the maximum allowed limit of 4 nested levels, or if required parameters are missing. |
76+
| 401 | Unauthorized. The subject token is invalid, expired, or the client credentials are incorrect. |
77+
| 403 | Forbidden. The client does not have permission to perform the token exchange, or the user does not have the requested scopes for the target audience. |
78+
79+
<ResponseExample>
80+
```json 200 Response
81+
{
82+
"access_token": "eyJ...",
83+
"expires_in": 86400,
84+
"token_type": "Bearer",
85+
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
86+
}
87+
```
88+
89+
```json 400 Response (Delegation chain limit exceeded)
90+
{
91+
"error": "invalid_request",
92+
"error_description": "Delegation chain (`act` claim) depth exceeds the maximum allowed limit of 5"
93+
}
94+
```
95+
</ResponseExample>
96+
97+
## Response Fields
98+
99+
<ResponseField name="access_token" type="string">
100+
The new Auth0 access token scoped for the downstream API. This [JSON Web Token (JWT)](/docs/secure/tokens/json-web-tokens#json-web-tokens) contains the same `sub` (user identity) as the subject token, with the `aud` set to the requested downstream API audience. The `act` claim tracks the delegation chain.
101+
</ResponseField>
102+
103+
<ResponseField name="issued_token_type" type="string">
104+
Confirms the format of the token returned. This matches the `requested_token_type` from your request.
105+
106+
Value: `urn:ietf:params:oauth:token-type:access_token`
107+
</ResponseField>
108+
109+
<ResponseField name="token_type" type="string">
110+
Specifies the authentication scheme to use in the Authorization header. For OBO, this is `Bearer` unless using DPoP, in which case `DPoP` will be used.
111+
</ResponseField>
112+
113+
<ResponseField name="expires_in" type="number">
114+
The lifetime of the token in seconds.
115+
</ResponseField>
116+
117+
<ResponseField name="scope" type="string">
118+
(Optional) Only included if the granted scopes differ from the requested scopes. Space-delimited list of scopes that were actually granted.
119+
</ResponseField>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
title: "Get Token"
3+
description: "The On-Behalf-Of (OBO) Token Exchange enables a middle-tier service to exchange an incoming user token for a new token scoped to the downstream service."
4+
---
5+
6+
`POST /oauth/token`
7+
8+
The On-Behalf-Of (OBO) Token Exchange ([RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693)) enables middle-tier services to to exchange a user-scoped token for a new token to call downstream services.
9+
10+
The new token maintains the original user's identity and permissions while being scoped specifically for the downstream service, enabling that service to make authorization decisions based on the end user. The delegation chain is tracked in the `act` (actor) claim, with each level representing a service in the call chain. To learn more, read the [On-Behalf-Of Token Exchange documentation](https://auth0.com/docs/secure/call-apis-on-users-behalf/on-behalf-of-token-exchange).
11+
12+
### Remarks
13+
14+
- Only Custom API clients associated with a resource server can use the OBO token exchange. A Custom API client has the following requirements:
15+
- Set `app_type` to `resource_server`.
16+
- Set `resource_server_identifier` to the valid resource server, i.e., https://my-api.example.com. Auth0 uses the resource server identifier as the audience parameter in authorization calls.
17+
18+
- The scopes issued to the application may differ from the scopes requested. In this case, a `scope` parameter will be included in the response JSON. Scopes are based on the user's [Role-Based Access Control (RBAC) policies](/docs/manage-users/access-control/rbac).
19+
20+
- OBO token exchanges trigger the [`post-login` Action trigger](/docs/customize/actions/explore-triggers/signup-and-login-triggers/login-trigger), where `event.transaction.protocol` is set to `oauth2-token-exchange` and `event.transaction.actor` tracks the complete delegation chain.
21+
22+
- The delegation chain is limited to five nested levels. The OBO token exchange will fail if the subject token already has five nested `act` levels.
23+
24+
- Cache access tokens for the lifetime of the token instead of requesting a new token for each API call. Access tokens are reusable until they expire; repeated token exchanges waste resources, increase latency, and may trigger rate limits.
25+
26+
## Parameters
27+
28+
<ParamField header="DPoP" type="string">
29+
A DPoP proof for the request. This is optional and only required if your application uses [Demonstrating Proof-of-Possession](/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop).
30+
</ParamField>
31+
32+
<ParamField header="auth0-forwarded-for" type="string">
33+
End-user IP as a string value. Set this if you want Suspicious IP Throttling protection to work in server-side scenarios.
34+
</ParamField>
35+
36+
## Request Body
37+
38+
<ParamField body="grant_type" type="string" required>
39+
Denotes the flow you are using. For On-Behalf-Of Token Exchange, use `urn:ietf:params:oauth:grant-type:token-exchange`.
40+
</ParamField>
41+
42+
<ParamField body="subject_token_type" type="string" required>
43+
The type of the subject token. For On-Behalf-Of Token Exchange, use `urn:ietf:params:oauth:token-type:access_token`.
44+
</ParamField>
45+
46+
<ParamField body="subject_token" type="string" required>
47+
The incoming Auth0 access token from the user or upstream service that the middle-tier service is currently holding.
48+
</ParamField>
49+
50+
<ParamField body="requested_token_type" type="string" required>
51+
Indicates what kind of token you want back. For On-Behalf-Of Token Exchange, use `urn:ietf:params:oauth:token-type:access_token`.
52+
</ParamField>
53+
54+
<ParamField body="client_id" type="string" required>
55+
Your Custom API client's Client ID. The Custom API client must be associated with a resource server (same identifier). As for other grant types, you can also pass the client ID in the Authorization header using HTTP Basic Auth.
56+
</ParamField>
57+
58+
<ParamField body="client_secret" type="string" required>
59+
Your Custom API client's Client Secret. As for other grant types, you can also pass the client secret in the Authorization header using HTTP Basic Auth. Other alternatives are also available as explained in [Auth0 Authentication API reference docs](https://auth0.com/docs/api/authentication#authentication-methods). Note that you cannot set `token_endpoint_auth_method` to `none` for OBO token exchange.
60+
</ParamField>
61+
62+
<ParamField body="audience" type="string" required>
63+
The unique identifier of the downstream API you want to access. This is the identifier for the downstream service that will receive and validate the new token.
64+
</ParamField>
65+
66+
<ParamField body="scope" type="string">
67+
(Optional) A space-delimited list of specific permissions requested for the downstream call. If not specified, all scopes granted to the user for the target audience will be included based on RBAC policies.
68+
</ParamField>
69+
70+
## Response
71+
72+
| Status | Description |
73+
|--------|-------------|
74+
| 200 | Successful response. Returns an access token for the downstream API with the same user identity preserved. |
75+
| 400 | Bad request. May occur if the delegation chain depth exceeds the maximum allowed limit of 4 nested levels, or if required parameters are missing. |
76+
| 401 | Unauthorized. The subject token is invalid, expired, or the client credentials are incorrect. |
77+
| 403 | Forbidden. The client does not have permission to perform the token exchange, or the user does not have the requested scopes for the target audience. |
78+
79+
<ResponseExample>
80+
```json 200 Response
81+
{
82+
"access_token": "eyJ...",
83+
"expires_in": 86400,
84+
"token_type": "Bearer",
85+
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
86+
}
87+
```
88+
89+
```json 400 Response (Delegation chain limit exceeded)
90+
{
91+
"error": "invalid_request",
92+
"error_description": "Delegation chain (`act` claim) depth exceeds the maximum allowed limit of 5"
93+
}
94+
```
95+
</ResponseExample>
96+
97+
## Response Fields
98+
99+
<ResponseField name="access_token" type="string">
100+
The new Auth0 access token scoped for the downstream API. This JWT contains the same `sub` (user identity) as the subject token, with the `aud` set to the requested downstream API audience. The `act` claim tracks the delegation chain.
101+
</ResponseField>
102+
103+
<ResponseField name="issued_token_type" type="string">
104+
Confirms the format of the token returned. This matches the `requested_token_type` from your request.
105+
106+
Value: `urn:ietf:params:oauth:token-type:access_token`
107+
</ResponseField>
108+
109+
<ResponseField name="token_type" type="string">
110+
Specifies the authentication scheme to use in the Authorization header. For OBO, this is `Bearer` unless using DPoP, in which case `DPoP` will be used.
111+
</ResponseField>
112+
113+
<ResponseField name="expires_in" type="number">
114+
The lifetime of the token in seconds.
115+
</ResponseField>
116+
117+
<ResponseField name="scope" type="string">
118+
(Optional) Only included if the granted scopes differ from the requested scopes. Space-delimited list of scopes that were actually granted.
119+
</ResponseField>

0 commit comments

Comments
 (0)