# Token Exchange Policy Lambda

Reference for the Token Exchange Policy lambda, which decides whether FusionAuth allows a token exchange request.

> For the index of this section of the site, see [llms.txt](https://fusionauth.io/docs/llms.txt)

> **PLAN:** This feature requires [an Enterprise plan](https://fusionauth.io/docs/get-started/core-concepts/plans-features.md#enterprise-features).

Available since `1.70.0`

A Token Exchange Policy lambda decides whether FusionAuth allows a [token exchange](https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/token-exchange.md) request, and which audiences, scopes, and lifetime the issued token receives. FusionAuth runs this lambda for every exchange in a Tenant that uses the Lambda policy engine. It runs after FusionAuth authenticates the exchanger and validates the subject token.

## Lambda Structure

The lambda must contain a function named `policy` with the following signature:

```javascript
function policy(result, exchange, context) {
  // Lambda code goes here
}
```

The function must declare all three parameters, even if it doesn't use them:

*   `result`: the object the lambda populates with its decision. This object is mutable.
*   `exchange`: information about the exchange request. This object is read-only.
*   `context`: information about the Tenant, plus access to [secrets](https://fusionauth.io/docs/extend/code/lambdas.md#secrets). This object is read-only.

The Admin UI fills a new lambda of this type with an example implementation.

### The `exchange` Object

`clientId`

*   Type: `String`

The client Id of the exchanger Entity.

`requestedAudiencesAndResources`

*   Type: `Array<String>`

The values of the `audience` and `resource` request parameters, in that order. Empty when the request includes neither.

`requestedScopes`

*   Type: `Array<String>`

The scopes from the `scope` request parameter. Empty when the request doesn't include `scope`.

`subjectTokenClaims`

*   Type: `Object`

The claims of the validated subject token, such as `sub`, `iss`, `aud`, `scope`, and `act`. Claim values keep the types they have in the token. For example, `scope` is a space-delimited string.

`subjectTokenValidationStatus`

*   Type: `String`

How FusionAuth validated the subject token. The possible values are:

*   `AUTHORITATIVE`: the subject token is an access token that this Tenant issued, verified with FusionAuth's own keys. FusionAuth uses this value when `subject_token_type` is `urn:ietf:params:oauth:token-type:access_token`.
*   `BEST_EFFORT`: the subject token is a JWT from an issuer in the Tenant's **Allowed issuers**, verified with keys from the issuer's OpenID Connect discovery document. FusionAuth uses this value when `subject_token_type` is `urn:ietf:params:oauth:token-type:jwt`.

### The `context` Object

`tenantId`

*   Type: `UUID`

The Id of the Tenant processing the exchange.

`services.secrets`

*   Type: `Object`

Provides access to [secrets](https://fusionauth.io/docs/extend/code/lambdas.md#secrets) through `context.services.secrets.get('<secret-name>')`.

### The `result` Object

The lambda sets **`result.decision`** to `ALLOW` or `DENY`, then populates **`result.granted`** or **`result.denial`**.

To allow an exchange, set all three fields of **`result.granted`**:

`granted.aud`

*   Type: `String or Array<String>`
*   Required

The audiences of the issued token.

`granted.scope`

*   Type: `String or Array<String>`
*   Required

The scopes of the issued token, as a space-delimited string or an array. To grant no scopes, use an empty array.

`granted.expires_in`

*   Type: `Integer`
*   Required

The lifetime of the issued token, in seconds.

Allow an exchange

```javascript
result.decision = 'ALLOW';
result.granted = {
  aud: ['email-service'],
  scope: ['email:send'],
  expires_in: 300
};
```

To deny an exchange, set both fields of **`result.denial`**:

`denial.error_code`

*   Type: `String`
*   Required

The OAuth error code to return to the client. The possible values are `invalid_request`, `invalid_target`, and `unauthorized_client`.

`denial.error_description`

*   Type: `String`
*   Required

A description of the denial to return to the client.

Deny an exchange

```javascript
result.decision = 'DENY';
result.denial = {
  error_code: 'unauthorized_client',
  error_description: 'This client may not exchange tokens for the requested audience.'
};
```

FusionAuth responds to a denied exchange with a `403` status code. The response contains the **`error_code`** value in **`error`**, the **`error_description`** value, and an **`error_reason`** of `lambda_denied`.

### Limits on the Grant

FusionAuth applies the following rules to an `ALLOW` decision before it builds the token:

*   FusionAuth drops any value in **`granted.aud`** that isn't in **`exchange.requestedAudiencesAndResources`**. If no values remain, FusionAuth denies the exchange with an `invalid_target` error.
*   When the subject token has a `scope` claim, FusionAuth drops any value in **`granted.scope`** that the subject token's `scope` claim doesn't contain. If the request included `scope` and no granted scopes remain, FusionAuth denies the exchange with an `invalid_scope` error.
*   FusionAuth sets the token lifetime to **`granted.expires_in`** or the default lifetime, whichever is shorter. The default lifetime is the exchanger's Entity Type JWT duration when the Entity Type has JWT configuration enabled, and the Tenant's JWT duration otherwise.

FusionAuth writes an Information entry to the [Event Log](https://fusionauth.io/docs/operate/troubleshooting.md#event-log) listing any audiences and scopes it dropped.

### Errors

FusionAuth treats the following as a lambda error and rejects the exchange:

*   The lambda throws an exception.
*   **`result.decision`** is neither `ALLOW` nor `DENY`.
*   An `ALLOW` decision is missing **`granted`** or any of its fields.
*   A `DENY` decision is missing **`denial`** or either of its fields, or uses an unsupported **`error_code`**.

FusionAuth responds to a lambda error with a `400` status code and an `invalid_request` error, and records the attempt in the token exchange audit log with the `POLICY_LAMBDA_ERROR` outcome. The response doesn't include exception details. To see them, enable debugging on the lambda.

## Assigning the Lambda

To assign the lambda in the Admin UI:

1.  Navigate to Tenants and click edit on the Tenant.
2.  Select the OAuth tab.
3.  In the Token exchange settings section, set **Token exchange policy engine** to `Lambda`.
4.  Select the lambda in **Token exchange policy lambda**.
5.  Click save.

To assign the lambda with the [Tenant API](https://fusionauth.io/docs/apis/tenants.md), set **`tenant.tokenExchangeConfiguration.policyEngine`** to `LAMBDA` and **`tenant.tokenExchangeConfiguration.policyEngineLambdaId`** to the lambda's Id.

## Examples

### Allow Specific Exchangers for Each Audience

This lambda maps each exchanger to the one audience it may call and the scopes it may receive. It grants the intersection of the requested scopes and the allowed scopes:

```javascript
function policy(result, exchange, context) {
  const rules = {
    'todo-service': { audience: 'email-service', scopes: ['email:send'] },
    'billing-service': { audience: 'invoice-service', scopes: ['invoice:read', 'invoice:write'] }
  };

  const rule = rules[exchange.clientId];
  if (!rule) {
    result.decision = 'DENY';
    result.denial = {
      error_code: 'unauthorized_client',
      error_description: 'This client may not exchange tokens.'
    };
    return;
  }

  if (exchange.requestedAudiencesAndResources.indexOf(rule.audience) === -1) {
    result.decision = 'DENY';
    result.denial = {
      error_code: 'invalid_target',
      error_description: 'The requested audience is not permitted for this client.'
    };
    return;
  }

  result.decision = 'ALLOW';
  result.granted = {
    aud: [rule.audience],
    scope: exchange.requestedScopes.filter(scope => rule.scopes.indexOf(scope) !== -1),
    expires_in: 300
  };
}
```

### Limit Tokens From External Issuers

This lambda accepts JWTs from external issuers, but only issues short-lived, read-only tokens for them:

```javascript
function policy(result, exchange, context) {
  const external = exchange.subjectTokenValidationStatus !== 'AUTHORITATIVE';

  result.decision = 'ALLOW';
  result.granted = {
    aud: exchange.requestedAudiencesAndResources,
    scope: external ? ['read'] : exchange.requestedScopes,
    expires_in: external ? 60 : 900
  };
}
```

### Call an External Authorization Service

This lambda asks an external service like [FusionAuth FGA by Permify](https://fusionauth.io/permify-docs.md) whether to allow the exchange. It reads the service's API key from a [secret](https://fusionauth.io/docs/extend/code/lambdas.md#secrets) and calls the service with [Lambda HTTP Connect](https://fusionauth.io/docs/extend/code/lambdas/lambda-remote-api-calls.md):

```javascript
function policy(result, exchange, context) {
  const response = fetch('https://authz.example.com/api/exchange', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + context.services.secrets.get('authz-api-key'),
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      exchanger: exchange.clientId,
      subject: exchange.subjectTokenClaims.sub,
      audiences: exchange.requestedAudiencesAndResources,
      scopes: exchange.requestedScopes
    })
  });

  if (response.status !== 200) {
    result.decision = 'DENY';
    result.denial = {
      error_code: 'invalid_request',
      error_description: 'The authorization service denied the exchange.'
    };
    return;
  }

  // assumes a response like {"aud": [...], "scope": [...], "expires_in": 300}
  const decision = JSON.parse(response.body);
  result.decision = 'ALLOW';
  result.granted = {
    aud: decision.aud,
    scope: decision.scope,
    expires_in: decision.expires_in
  };
}
```

## Debugging

To troubleshoot a policy lambda:

*   Enable debugging on the lambda to write `console.debug()` output and exception details to the [Event Log](https://fusionauth.io/docs/operate/troubleshooting.md#event-log). See [Console](https://fusionauth.io/docs/extend/code/lambdas.md#console).
*   Enable **Enable debug logging** in the Tenant's token exchange settings to write the reason for each failed exchange to the Event Log.
*   Review the outcome and policy decision for each attempt in the Admin UI under System -> Token Exchange Log , or with the [Search the Token Exchange Audit Log](https://fusionauth.io/docs/apis/system/search-the-token-exchange-audit-log.md) API.

Related

[APIs Retrieve a Token Exchange Chain API API documentation for the FusionAuth Retrieve a Token Exchange Chain API. rfc 8693](https://fusionauth.io/docs/apis/system/retrieve-a-token-exchange-chain.md)

[APIs Search the Token Exchange Audit Log API API documentation for the FusionAuth Search the Token Exchange Audit Log API. rfc 8693](https://fusionauth.io/docs/apis/system/search-the-token-exchange-audit-log.md)

[Extend Token Exchange Populate Lambda Reference for the Token Exchange Populate lambda, which adds or narrows claims in tokens issued through token exchange. rfc 8693](https://fusionauth.io/docs/extend/code/lambdas/token-exchange-populate.md)

[Extend Token Exchange Event Learn about the Token Exchange event. rfc 8693](https://fusionauth.io/docs/extend/events-and-webhooks/events/token/token-exchange.md)
---

## Other pages in Lambdas

> For the full index of this section, see [Extend](https://fusionauth.io/docs/llms-extend.txt).

- [CIMD Client Registration Lambda](https://fusionauth.io/docs/extend/code/lambdas/cimd-client-registration.md): An overview of the CIMD client registration lambda.
- [Client Credentials JWT Populate Lambda](https://fusionauth.io/docs/extend/code/lambdas/client-credentials-jwt-populate.md): An overview of the Client Credentials JWT Populate lambda.
- [Lambdas](https://fusionauth.io/docs/extend/code/lambdas.md): An overview of FusionAuth's lambdas, which are pluggable JavaScript code that run at various points in the user lifecycle.
- [JWT Populate Lambda](https://fusionauth.io/docs/extend/code/lambdas/jwt-populate.md): An overview of the JWT populate lambda.
- [Call an API from a Lambda](https://fusionauth.io/docs/extend/code/lambdas/lambda-remote-api-calls.md): An overview of how to make API calls from Lambdas.
- [Login Validation Lambda](https://fusionauth.io/docs/extend/code/lambdas/login-validation.md): An overview of the login validation lambda.
- [MFA Requirement Lambda](https://fusionauth.io/docs/extend/code/lambdas/mfa-requirement.md): An overview of the MFA requirement lambda.
- [Apple Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/apple-reconcile.md): An overview of the Apple Reconcile lambda.
- [Epic Games Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/epic-games-reconcile.md): An overview of the Epic Games Reconcile lambda.
- [External JWT Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/external-jwt-reconcile.md): An overview of the External JWT Reconcile lambda.
- [Facebook Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/facebook-reconcile.md): An overview of the Facebook Reconcile lambda.
- [Google Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/google-reconcile.md): An overview of the Google Reconcile lambda.
- [HYPR Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/hypr-reconcile.md): An overview of the HYPR Reconcile lambda.
- [LDAP Connector Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/ldap-connector-reconcile.md): An overview of the LDAP Connector Reconcile lambda.
- [LinkedIn Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/linkedin-reconcile.md): An overview of the LinkedIn Reconcile lambda.
- [Nintendo Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/nintendo-reconcile.md): An overview of the Nintendo Reconcile lambda.
- [OpenID Connect Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/openid-connect-response-reconcile.md): An overview of the OpenID Connect Response Reconcile lambda.
- [SAML v2 Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/samlv2-response-reconcile.md): An overview of the SAML v2 Response Reconcile lambda.
- [Sony PlayStation Network Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/sony-playstation-network-reconcile.md): An overview of the Sony PlayStation Network Reconcile lambda.
- [Steam Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/steam-reconcile.md): An overview of the Steam Reconcile lambda.
- [Twitch Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/twitch-reconcile.md): An overview of the Twitch Reconcile lambda.
- [Twitter Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/twitter-reconcile.md): An overview of the Twitter Reconcile lambda.
- [Xbox Reconcile Lambda](https://fusionauth.io/docs/extend/code/lambdas/reconcile/xbox-reconcile.md): An overview of the Xbox Reconcile lambda.
- [SAML v2 Populate Lambda](https://fusionauth.io/docs/extend/code/lambdas/samlv2-response-populate.md): An overview of the SAML v2 populate lambda.
- [SCIM Group Request Converter Lambda](https://fusionauth.io/docs/extend/code/lambdas/scim-group-request-converter.md): An overview of the SCIM Group Request Converter lambda.
- [SCIM Group Response Converter Lambda](https://fusionauth.io/docs/extend/code/lambdas/scim-group-response-converter.md): An overview of the SCIM Group Response Converter lambda.
- [SCIM User Request Converter Lambda](https://fusionauth.io/docs/extend/code/lambdas/scim-user-request-converter.md): An overview of the SCIM User Request Converter lambda.
- [SCIM User Response Converter Lambda](https://fusionauth.io/docs/extend/code/lambdas/scim-user-response-converter.md): An overview of the SCIM User Response Converter lambda.
- [Self-Service Registration Validation Lambda](https://fusionauth.io/docs/extend/code/lambdas/self-service-registration.md): An overview of the Self-Service Registration Validation lambda.
- [Test Lambdas](https://fusionauth.io/docs/extend/code/lambdas/testing.md): A guide on how to create and test lambdas with FusionAuth.
- [Token Exchange Populate Lambda](https://fusionauth.io/docs/extend/code/lambdas/token-exchange-populate.md): Reference for the Token Exchange Populate lambda, which adds or narrows claims in tokens issued through token exchange.
- [UserInfo Populate Lambda](https://fusionauth.io/docs/extend/code/lambdas/userinfo-populate.md): An overview of the UserInfo populate lambda.
