---
title: Securing the OAuth 2.0 access token endpoint with PingOne Advanced Identity Cloud
description: Configure PingGateway to transform OAuth 2.0 grant-type requests into secure JWT bearer grants and propagate them to PingOne Advanced Identity Cloud
component: pinggateway
version: 2026
page_id: pinggateway:aic:grant-swap
canonical_url: https://docs.pingidentity.com/pinggateway/2026/aic/grant-swap.html
revdate: 2026-01-15
keywords: ["Grant swap", "OAuth 2.0"]
page_aliases: ["identity-cloud-guide:grant-swap.adoc"]
---

# 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.1/am-oauth2/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.1/am-oauth2/oauth2-client-cred-grant.html) requests or [Resource owner password credentials grant](https://docs.pingidentity.com/pingam/8.1/am-oauth2/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/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/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.
