Secure the OAuth 2.0 access token endpoint
This section 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.
Use GrantSwapJwtAssertionOAuth2ClientFilter to increase the security of less-secure grant-type requests, such as Client credentials grant requests or Resource owner password credentials grant requests.
The GrantSwapJwtAssertionOAuth2ClientFilter obtains access tokens from the
Consider the following options to secure access to the GrantSwapJwtAssertionOAuth2ClientFilter:
|
The following figure shows the flow of information for a grant swap:
Before you start, prepare PingOne Advanced Identity Cloud, PingGateway, and the sample application as described in Example installation for this guide.
-
Set up PingOne Advanced Identity Cloud:
-
Log in to the PingOne Advanced Identity Cloud admin UI as an administrator.
-
Create a service account with the following values, as described in 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.
-
-
Make a note of the service account ID and download the private key to your secrets directory.
-
Rename the key to match the regex format
(\.[a-zA-Z0-9])*
. For example, renamemyServiceAccount_privateKey.jwk
toprivateKey.jwk
.
-
-
Set up PingGateway:
-
Set up PingGateway for HTTPS, as described in Configure PingGateway for TLS (server-side).
-
Add the following route to PingGateway:
-
Linux
-
Windows
$HOME/.openig/config/routes/grant-swap.json
%appdata%\OpenIG\config\routes\grant-swap.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" } } }
-
-
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
-
-
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
, andexpiryTime
. -
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 beservice-account
.
-
-
Add the following route to PingGateway to return a standard OAuth 2.0 error response if the request fails the route condition:
-
Linux
-
Windows
$HOME/.openig/config/routes/zz-returns-invalid-request.json
%appdata%\OpenIG\config\routes\zz-returns-invalid-request.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\"}" } } }
-
-
-
Test the setup by accessing the route with a
curl
command similar to this:$ 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:*' {"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.