---
title: Securing the OAuth 2.0 access token endpoint with PingOne Advanced Identity Cloud
description: This page uses a GrantSwapJwtAssertionOAuth2ClientFilter to transform requests for OAuth 2.0 access tokens into secure JWT bearer grant type requests. It propagates the transformed requests to PingOne Advanced Identity Cloud to obtain an access token.
component: pinggateway
version: 2025.9
page_id: pinggateway:aic:grant-swap
canonical_url: https://docs.pingidentity.com/pinggateway/2026/aic/grant-swap.html
llms_txt: https://docs.pingidentity.com/pinggateway/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
keywords: ["Grant swap", "OAuth 2.0"]
page_aliases: ["identity-cloud-guide:grant-swap.adoc"]
superseded_by: https://docs.pingidentity.com/pinggateway/2026/aic/grant-swap.html
---

# Securing the OAuth 2.0 access token endpoint with PingOne Advanced Identity Cloud

This page uses a [GrantSwapJwtAssertionOAuth2ClientFilter](../reference/GrantSwapJwtAssertionOAuth2ClientFilter.html) to transform requests for OAuth 2.0 access tokens into secure [JWT bearer grant type](https://docs.pingidentity.com/pingam/8/oauth2-guide/oauth2-jwt-bearer-grant.html) requests. It propagates the transformed requests to PingOne Advanced Identity Cloud to obtain an access token.

Use GrantSwapJwtAssertionOAuth2ClientFilter to increase the security of less-secure grant-type requests, such as [Client credentials grant](https://docs.pingidentity.com/pingam/8/oauth2-guide/oauth2-client-cred-grant.html) requests or [Resource owner password credentials grant](https://docs.pingidentity.com/pingam/8/oauth2-guide/oauth2-ropc-grant.html) requests.

|   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The GrantSwapJwtAssertionOAuth2ClientFilter obtains access tokens from the `/oauth2/access_token` endpoint. To prevent unwanted or malicious access to the endpoint, make sure only a well-defined set of clients can use this filter.Consider the following options to secure access to the GrantSwapJwtAssertionOAuth2ClientFilter:- Deploy PingGateway on a trusted network.

- Use mutual TLS (mTLS) and X.509 certificates for authentication between clients and PingGateway. For more information, refer to [OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-mtls).

- Configure an [AllowOnlyFilter](../reference/AllowOnlyFilter.html) in front of the GrantSwapJwtAssertionOAuth2ClientFilter to control access within a route.

- Define restrictive [Route](../reference/Route.html) conditions to allow access only for expected grant-type requests. For example, define a route condition that requires a specific client ID, grant-type, or scope.

- Configure a [ScriptableFilter](../reference/ScriptableFilter.html) in front of the GrantSwapJwtAssertionOAuth2ClientFilter to validate requests. |

The following figure shows the flow of information for a grant swap:

![GrantSwapJwtAssertionOAuth2ClientFilter](_images/GrantSwapJwtAssertionOAuth2ClientFilter.svg)

Before you start, prepare PingOne Advanced Identity Cloud, PingGateway, and the sample application as described in [Example installation for this guide](preface.html#preface-examples).

1. Set up PingOne Advanced Identity Cloud:

   1. Log in to the Advanced Identity Cloud admin UI as an administrator.

   2. Create a service account with the following values, as described in [Create a new service account](https://docs.pingidentity.com/pingoneaic/latest/tenants/service-accounts.html#create_a_new_service_account):

      * Name: `myServiceAccount`

      * Scopes: `fr:idm:* All Identity Management APIs`

        The service account ID is displayed and you are prompted to download the private key. The public key is held in PingOne Advanced Identity Cloud.

        For more information, refer to [Service accounts](https://docs.pingidentity.com/pingoneaic/latest/tenants/service-accounts.html).

   3. Make a note of the service account ID and download the private key to your secrets directory.

   4. Rename the key to match the regex format `(\.[a-zA-Z0-9])*`. For example, rename `myServiceAccount_privateKey.jwk` to `privateKey.jwk`.

2. Set up PingGateway:

   1. Set up PingGateway for HTTPS, as described in [Configure PingGateway for TLS (server-side)](../installation-guide/securing-connections.html#server-side-tls).

   2. Add the following route to PingGateway:

      * Linux

        `$HOME/.openig/config/routes/grant-swap.json`

      * Windows

        `%appdata%\OpenIG\config\routes\grant-swap.json`

      ```json
      {
        "name" : "grant-swap",
        "properties": {
          "idcInstanceUrl": "https://myTenant.forgeblocks.com",
          "issuer": "service-account-id",
          "secretsDir": "path-to-secrets",
          "privateKeyFilename": "privateKey.jwk"
        },
        "condition" : "#{find(request.uri.path, '^/am/oauth2/access_token') && request.entity.form['grant_type'][0] == 'client_credentials'}",
        "baseURI" : "&{idcInstanceUrl}:443/",
        "heap" : [ {
          "name": "JwkPropertyFormat-01",
          "type": "JwkPropertyFormat"
        },
          {
            "name": "FileSystemSecretStore-01",
            "type": "FileSystemSecretStore",
            "config": {
              "format": "JwkPropertyFormat-01",
              "directory": "&{secretsDir}",
              "mappings": [ {
                "secretId": "&{privateKeyFilename}",
                "format": "JwkPropertyFormat-01"
              }
              ]
            }
          }
        ],
        "handler" : {
          "type" : "Chain",
          "capture" : "all",
          "config" : {
            "filters" : [
              {
                "name" : "GrantSwapJwtAssertionOAuth2ClientFilter-01",
                "description": "access /access_token endpoint with jwt-bearer-profile",
                "type" : "GrantSwapJwtAssertionOAuth2ClientFilter",
                "capture" : "all",
                "config" : {
                  "clientId" : "service-account",
                  "assertion" : {
                    "issuer" : "&{issuer}",
                    "audience" : "&{idcInstanceUrl}/am/oauth2/access_token",
                    "subject" : "&{issuer}",
                    "expiryTime": "2 minutes"
                  },
                  "signature": {
                    "secretId": "&{privateKeyFilename}",
                    "includeKeyId": false
                  },
                  "secretsProvider": "FileSystemSecretStore-01",
                  "scopes" : {
                    "type": "RequestFormResourceAccess"
                  }
                }
              }
            ],
            "handler" : "ForgeRockClientHandler"
          }
        }
      }
      ```

      Source: [grant-swap.json](../_attachments/config/routes/grant-swap.json)

   3. In the route, replace the values for the following properties with your values:

      * `idcInstanceUrl`: The root URL of your PingOne Advanced Identity Cloud tenant.

      * `issuer`: The ID of the service account created in PingOne Advanced Identity Cloud

      * `secretsDir`: The directory containing the downloaded private key

      * `privateKeyFilename`: The filename of the downloaded private key

   4. Notice the following features of the route:

      * The condition intercepts only `client_credentials` grant-type requests on the path `/am/oauth2/access_token`. A more secure condition can be set on the client ID.

      * Requests are rebased to the PingOne Advanced Identity Cloud URL.

      * A FileSystemSecretStore loads the private-key JWK used to sign the JWT.

      * The GrantSwapJwtAssertionOAuth2ClientFilter:

        * Requires the core JWT claims `issuer`, `subject`, `audience`, and `expiryTime`.

        * Uses RequestFormResourceAccess to extract scopes from the inbound request for inclusion in the JWT-assertion grant-type request propagated to AM.

        * Signs the JWT with the JWK provided by the service account.

      * The GrantSwapJwtAssertionOAuth2ClientFilter `clientId` refers to the OAuth 2.0 client ID created by AM. The value must be `service-account`.

   5. Add the following route to PingGateway to return a standard OAuth 2.0 error response if the request fails the route condition:

      * Linux

        `$HOME/.openig/config/routes/zz-returns-invalid-request.json`

      * Windows

        `%appdata%\OpenIG\config\routes\zz-returns-invalid-request.json`

      ```json
      {
        "name" : "zz-returns-invalid-request",
        "handler" : {
          "type" : "StaticResponseHandler",
          "capture" : "all",
          "config" : {
            "status": 400,
            "headers": {"Content-Type": ["application/json; charset=UTF-8"]},
            "entity": "{\"error\": \"Invalid_request\", \"error_description\": \"Invalid request\"}"
          }
        }
      }
      ```

      Source: [zz-returns-invalid-request.json](../_attachments/config/routes/zz-returns-invalid-request.json)

3. Test the setup by accessing the route with a `curl` command similar to this:

   ```console
   $ curl  \
       --cacert /path/to/secrets/ig.example.com-certificate.pem \
       --location \
       --request POST 'https://ig.example.com:8443/am/oauth2/access_token' \
       --header 'Content-Type: application/x-www-form-urlencoded' \
       --data-urlencode 'client_id=myServiceAccount' \
       --data-urlencode 'grant_type=client_credentials' \
       --data-urlencode 'scope=fr:idm:*'
   ```

   Output

   ```json
   {"access_token":"eyJ...","scope":"fr:idm:*","token_type":"Bearer","expires_in":899}
   ```

   The command makes a `client_credentials` grant-type request on the path `/am/oauth2/access_token`, supplying the client ID and scopes. PingGateway transforms the request into a JWT-assertion grant-type request and propagates it to PingOne Advanced Identity Cloud.

   Because the service account in PingOne Advanced Identity Cloud supports the requested scope, the GrantSwapJwtAssertionOAuth2ClientFilter returns an access token.
