Configure a PKCE authorization workflow


Introduction

Applications defined as NATIVE_APP types that use an authorization code grant can be compromised by authorization code interception attacks. The attacking application gains access to the client secret, intercepts the authorization code, and is able to exchange the intercepted authorization code for an access token.

Proof Key for Code Exchange (PKCE) authorization requests specify additional parameters in the request to prevent malicious apps from intercepting the authorization code. PKCE uses a random key, a code_verifier, that is used to compute a code_challenge parameter, which functions like a temporary application secret (unique to a single token request). PKCE works as follows:

  1. The client creates and records a code_verifier secret, which is a random value between 43 and 128 characters in length.

  2. The client uses the code_verifier value to compute the code_challenge value. The code_challenge_method is the transformation method that creates the code_challenge value. This parameter value is also recorded.

  3. The authorization request includes the code_challenge and in some cases the code_challenge_method parameter values in the request. The code_challenge_method is an optional parameter. It defaults to plain if not specified (which generates an error when the S256_REQUIRED PKCE enforcement option is specified by the application).

  4. The authorization server records the code_challenge and the code_challenge_method parameter values, and responds by issuing the authorization code.

  5. The client sends the authorization code to the /{environmentId}/as/token endpoint. The token request requires the code_verifier secret created in step 1.

  6. The authorization server uses the code_challenge_method to transform the code_verifier value and compare it to the code_challenge value submitted and recorded in the authorize request.

  7. If these values are equal, an access token is granted. If they are not equal, access is denied.

Workflow tasks

This scenario illustrates the following operations supported by the PingOne for Customers APIs:

  • Create an application and set its pkceEnforcement property.

  • Create an authorization request that includes code_challenge and code_challenge_method parameter values.

  • Create a token request that includes the code_verifier secret.

Workflow order of operations

To enable a PKCE authorization workflow, the following tasks must be completed successfully:

  1. Make a POST request to /environments/{environmentId}/applications to define an OpenID Connect native app type that uses an authorization code grant.

  2. Make a GET request to /{environmentId}/as/authorize to initiate authorization and submit the code_challenge and code_challenge_method values to the authorization server.

  3. Make a POST request to /{environmentId}/as/token to exchange the authorization code for an access token.

Step 1: Create the application connection

The POST /environments/{environmentId}/applications endpoint creates the application connection and sets the pkceEnforcement property to one of the REQUIRED options.

curl -X "POST" "https://api.pingone.com/v1/environments/{environmentId}/applications" \
-H 'Content-type: application/json' \
-H 'Authorization: Bearer jwtToken' \
-d '{
  {
    "name": "MyApp",
    "description": "this is my application",
    "pkceEnforcement": "S256_REQUIRED",
    "enabled": true,
    "type": "NATIVE_APP",
    "protocol": "OPENID_CONNECT",
    "responseTypes": [
        "CODE",
        "TOKEN",
        "ID_TOKEN"
    ],
    "grantTypes": [
        "AUTHORIZATION_CODE",
        "IMPLICIT",
        "REFRESH_TOKEN"
    ],
    "tokenEndpointAuthMethod": "NONE",
    "postLogoutRedirectUris": [
        "https://example.com"
    ],
    "redirectUris": [
        "https://example.com"
    ]
}
}'

The response returns a 201 Created message and shows the application connection data. In this request, the pkceEnforcement property value is set to S256_REQUIRED, which specifies that a PKCE code_challenge parameter is required in the authorize request and the code_challenge_method is also required and must specify S256 (a SHA2 256-bit hash).

{
  "_links": {
    "self": {
      "href": "https://api.pingone.com/v1/environments/9ad15e9e-3ac6-43f7-a053-d46b87d6c4a7/applications/9a40c076-fe07-4683-87ff-d9710e7f870a"
    },
    "environment": {
      "href": "https://api.pingone.com/v1/environments/9ad15e9e-3ac6-43f7-a053-d46b87d6c4a7"
    },
    "attributes": {
      "href": "https://api.pingone.com/v1/environments/9ad15e9e-3ac6-43f7-a053-d46b87d6c4a7/applications/9a40c076-fe07-4683-87ff-d9710e7f870a/attributes"
    },
    "secret": {
      "href": "https://api.pingone.com/v1/environments/9ad15e9e-3ac6-43f7-a053-d46b87d6c4a7/applications/9a40c076-fe07-4683-87ff-d9710e7f870a/secret"
    },
    "grants": {
      "href": "https://api.pingone.com/v1/environments/9ad15e9e-3ac6-43f7-a053-d46b87d6c4a7/applications/9a40c076-fe07-4683-87ff-d9710e7f870a/grants"
    }
  },
  "environment": {
    "id": "9ad15e9e-3ac6-43f7-a053-d46b87d6c4a7"
  },
  "id": "9a40c076-fe07-4683-87ff-d9710e7f870a",
  "name": "MyApp",
  "description": "this is my application",
  "enabled": true,
  "type": "NATIVE_APP",
  "protocol": "OPENID_CONNECT",
  "createdAt": "2019-11-19T17:26:25.820Z",
  "updatedAt": "2019-11-19T17:26:25.820Z",
  "responseTypes": [
    "CODE",
    "ID_TOKEN",
    "TOKEN"
  ],
  "grantTypes": [
    "REFRESH_TOKEN",
    "AUTHORIZATION_CODE",
    "IMPLICIT"
  ],
  "tokenEndpointAuthMethod": "NONE",
  "pkceEnforcement": "S256_REQUIRED",
  "postLogoutRedirectUris": [
    "https://example.com"
  ],
  "redirectUris": [
    "https://example.com"
  ]
}

Step 2: Submit the authorize request

You can use the /{environmentId}/as/authorize endpoint to initiate authorization. This request must include the code_challenge and code_challenge_method parameters.

https://auth.pingone.com/{environmentId}/as/authorize?response_type=code&client_id={applicationId}&redirect_uri=https://example.com&scope=openid&code_challenge={codeChallenge}&code_challenge_method=S256

In the request, the response_type property specifies that the request returns an authorization code that can be exchanged for a token. The client_id property identifies the application ID for the application you created in Step 1. The code_challenge value is computed using the code_verifier prior to submitting the authorize request. The code_challenge_method value specifies the S256 method.

The request returns a Location HTTP header that specifies the URL for the sign-on screen and the flow ID for this specific authentication workflow. The user’s browser is redirected to the sign-on screen to enter account credentials, usually a username and password. For more information about sign-on flows, see Authentication workflow walkthrough.

Step 3: Get the token

You can use the /{environmentId}/as/token endpoint to obtain an access token by presenting its authorization grant. In this activity, the token request identifies authorization_code as the grant type, and it must also provide the code value returned in Step 2.

With the pkceEnforcment property enabled on the application, the token request must provide the code_verifier parameter value. This parameter is used to verify the code_challenge value submitted in the authorization request in Step 2.

curl -X POST \
  'https://auth.pingone.com/{environmentId}/as/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code&code={authorizationCode}&client_id={clientId}&redirect_uri=https://example.com&code_verifier={codeVerifier}'

The token request transforms the code_verifier property value using the code_challenge_method specified in the authorize request. If the transformed code_verifier value is equal to the code_challenge value submitted in the authorize request, then the authorization server issues the token.