Identity Cloud

Authorization code grant with PKCE

The authorization code grant, when combined with the Proof Key for Code Exchange (PKCE) standard (RFC 7636), is used when a public client, such as a native or SPA application, requires access to protected resources.

The flow is similar to the regular authorization code grant, but the client must generate a code that is used in the communication between the client and the authorization server. This code mitigates against interception attacks performed by malicious users.

Browser-based clients making OAuth 2.0 requests to different domains must implement Cross-Origin Resource Sharing (CORS) calls to access OAuth 2.0 resources in different domains.

The PKCE flow adds three parameters to those used for the authorization code grant:

code_verifier

A random string that correlates the authorization request with the token request.

code_challenge

A string derived from the code verifier sent in the authorization request, which is validated against the code verifier during the token request.

code_challenge_method

The method used to derive the code challenge.

The authorization code grant with PKCE flow

OAuth 2.0 authorization code grant with PKCE flow
Figure 1. OAuth 2.0 authorization code grant with PKCE flow
  1. The client receives a request to access a protected resource. To access the resource, the client requires authorization from the resource owner. When using the PKCE standard, the client must generate a unique code and a way to verify it, and append the code to the request for the authorization code.

  2. The client redirects the resource owner’s user-agent to the authorization server.

  3. The authorization server authenticates the resource owner, confirms resource access, and gathers consent if not previously saved.

  4. If the resource owner’s credentials are valid, the authorization server stores the code challenge and redirects the resource owner’s user agent to the redirection URI.

  5. During the redirection process, the authorization server appends an authorization code to the request to the client.

  6. The client receives the authorization code and calls the authorization server’s token endpoint to exchange the authorization code for an access token appending the verification code to the request.

  7. The authorization server verifies the code stored in memory using the validation code. It also verifies the authorization code. If both codes are valid, the authorization server returns an access token (and a refresh token, if configured) to the client.

  8. The client requests access to the protected resource from the resource server.

  9. The resource server contacts the authorization server to validate the access token.

  10. The authorization server validates the token and responds to the resource server.

  11. If the token is valid, the resource server allows the client to access the protected resource.

Demonstrate the authorization code grant with PKCE flow

Perform these steps to get an authorization code and exchange it for an access token:

Prepare the demonstration

Complete these steps to prepare the authorization code grant with PKCE flow demonstration:

  1. Create an application owner profile and record the username and password.

  2. Register a client application.

    1. In the Identity Cloud admin UI, go to Applications and select + Custom Application.

    2. Select the sign-in method as OIDC - OpenId Connect and application type as Native / SPA.

    3. Provide the client application details, including:

      Name

      myClient

      Owners

      <application-owner>

      Client ID

      myClient

    4. Under Sign On > General Settings, set these fields to have the following values:

      Sign-in URLs

      https://www.example.com:443/callback

      Scopes

      write

  3. Create a resource owner profile and record the username and password.

Generate a code verifier and a code challenge

The client application must generate a code verifier, a high-entropy URL-safe random string between 43 and 128 characters long, and a code challenge, a base64url-encoded hash of the code verifier.

It is mandatory to create the challenge using a SHA-256 algorithm if the client supports it, as specified in the PKCE standard (RFC 7636).

This example JavaScript code generates values such as 082b7ab3042995bcb3163ec83cf5f348ff4393d5713630eb5f09dcf7d0c2cca39749313556c260558eb49355ff86d0e61449 for the code verifier and K7Dz7AcV1urbgo4FYNgy2QAAz6v2LyIdmmGPzsFZbAc for the code challenge:

const crypto = require('crypto');

const verifier = crypto.randomBytes(50).toString('hex').slice(0, 128);
const challenge = crypto.createHash('sha256')
    .update(Buffer.from(verifier))
    .digest('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');

console.log("verifier: " + verifier);
console.log("challenge: " + challenge);

The client is now ready to request an authorization code.

Get an authorization code using a browser

  1. The client redirects the resource owner’s user-agent to the authorization server’s /oauth2/authorize endpoint, including the following query parameters:

    • client_id=myClient

    • response_type=code

    • scope: write

    • redirect_uri: https://www.example.com:443/callback

    • code_challenge=<code_challenge>

    • code_challenge_method=S256

    The Code Verifier Parameter Required setting in the AM admin UI (Realms > Realm Name > Services > OAuth2 Provider > Advanced) specifies whether AM requires clients to include a code verifier in their calls.

    However, if a client makes a call to AM with the code_challenge parameter, AM will honor the code exchange regardless of the value of Code Verifier Parameter Required. For more information, refer to Authorization server configuration.

    For example:

    https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/authorize
    ?client_id=myClient
    &response_type=code
    &scope=write
    &redirect_uri=https://www.example.com:443/callback
    &code_challenge=K7Dz7AcV1urbgo4FYNgy2QAAz6v2LyIdmmGPzsFZbAc
    &code_challenge_method=S256
    &state=abc123

    The URL is split and spaces added for readability purposes.

    The scope parameter is optional if default values are configured in the authorization server or the client.

    The state parameter is included to protect against CSRF attacks but is also optional.

  2. The resource owner authenticates to the authorization server. In this demonstration, they sign in using the default journey configured for the realm.

    By default, client applications in Identity Cloud use implied consent. If Identity Cloud is configured to require explicit consent, the authorization server presents the resource owner with a consent screen. To continue the flow, the resource owner must select Allow to grant consent.

    The authorization server redirects the resource owner to the URL specified in the redirect_uri parameter.

  3. Inspect the URL in the browser.

    It contains a code parameter with the authorization code the authorization server has issued.

    For example:

    https://www.example.com/callback?code=<authorization-code>&iss…​

  4. Follow the steps to get an access token.

Get an authorization code using REST

  1. Authenticate as the resource owner.

    For example:

    $ curl \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'X-OpenAM-Username: <resource-owner-username>' \
    --header 'X-OpenAM-Password: <resource-owner-password>' \
    --header "Accept-API-Version: resource=2.0, protocol=1.0" \
    'https://<tenant-env-fqdn>/am/json/realms/root/realms/alpha/authenticate'
    {"tokenId":"<tokenId>","successUrl":"/enduser/?realm=/alpha","realm":"/alpha"}
  2. As the client, call the /oauth2/authorize endpoint to request the authorization code. Provide the resource owner’s SSO token in a cookie and the following parameters:

    • scope: write

    • response_type: code

    • client_id: myClient

    • csrf: <tokenId>

    • redirect_uri: https://www.example.com:443/callback

    • decision: allow

    • code_challenge: <code_challenge>

    • code_challenge_method: S256

      The Code Verifier Parameter Required setting in the AM admin UI (Realms > Realm Name > Services > OAuth2 Provider > Advanced) specifies whether AM requires clients to include a code verifier in their calls.

      However, if a client makes a call to AM with the code_challenge parameter, AM will honor the code exchange regardless of the value of Code Verifier Parameter Required. For more information, refer to Authorization server configuration.

      For example:

      $ curl --dump-header - \
      --request POST \
      --Cookie "<session-cookie-name>=<tokenId>" \
      --data "scope=write" \
      --data "response_type=code" \
      --data "client_id=myClient" \
      --data "csrf=<tokenId>" \
      --data "redirect_uri=https://www.example.com:443/callback" \
      --data "state=abc123" \
      --data "decision=allow" \
      --data "code_challenge=K7Dz7AcV1urbgo4FYNgy2QAAz6v2LyIdmmGPzsFZbAc" \
      --data "code_challenge_method=S256" \
      "https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/authorize"

      The scope parameter is optional if default values are configured in the authorization server or the client.

      The state parameter is included to protect against CSRF attacks but is also optional.

      If the authorization server is able to authenticate the user and the client, it returns an HTTP 302 response with the authorization code appended to the redirection URL:

      HTTP/2 302
      …​
      location: https://www.example.com:443/callback?code=<authorization-code>&iss…​
      …​
  3. Follow the steps to get an access token.

Exchange an authorization code for an access token

  1. As the client, call the /oauth2/access_token endpoint to exchange the authorization code for an access token. Provide the following parameters:

    • client_id: myClient

    • grant_type: authorization_code

    • code: <authorization-code>

    • redirect_uri: https://www.example.com:443/callback

    • code_verifier: <code-verifier>

    For example:

    $ curl \
    --request POST \
    --data "client_id=myClient" \
    --data "grant_type=authorization_code" \
    --data "code=<authorization-code>" \
    --data "redirect_uri=https://www.example.com:443/callback" \
    --data "code_verifier=082b7ab3042995bcb3163ec83cf..86d0e61449" \
    "https://<tenant-env-fqdn>/am/oauth2/realms/root/realms/alpha/access_token"

    The client_id and the redirection_uri parameters specified in this call must match those used for the authorization code request, or the authorization server will not validate the code.

    The authorization server returns an access token, for example:

    {
      "access_token": "<access-token>",
      "refresh_token":"<refresh-token>",
      "scope": "write",
      "token_type": "Bearer",
      "expires_in": 3599
    }

    By default, the authorization server also issues a refresh token whenever it issues access tokens.

Copyright © 2010-2023 ForgeRock, all rights reserved.