Configuring OIDC Authorization code flow with PKCE

This tutorial describes how to configure OIDC Authorization code flow with PKCE in CAS access manager.

What is PKCE

PKCE stands for Proof of Key for Code Exchange and it is basically used for native mobile or desktop applications and/or single-page JavaScript web applications. The point of using PKCE is that no client secret needs to be placed in the application.

In the context of PKCE and client-side only applications, the Cross-Origin Resource Sharing (CORS) must usually be configured for everything to work properly.

PKCE Implementation

Building on top of the OIDC Authorization code flow, there are three new parameters used by PKCE:

  • The code_verifier is a cryptographically random string generated by your application. This dynamically created string is used to correlate the final access token request with the initial authorization request.

  • The code_challenge is derived from the code_verifier using one of the two possible transformations: plain and S256.

  • The code_challenge_method tells the server which function was used to transform the code_verifier (plain or S256). It defaults to plain if not explicitly set.

See RFC 7636 for complete specifications.

CAS OIDC service with PKCE

Example OIDC service configuration with PKCE
{
  "@class" : "org.apereo.cas.services.OidcRegisteredService",
  "clientId": "vueappjs",
  "serviceId" : "^http://testjs.example.com:8080/auth/signinwin/main",
  "name": "vueapp",
  "id": 202,
  "scopes" : [ "java.util.HashSet", [ "openid" ]],
  "supportedGrantTypes": [ "java.util.HashSet", [ "AUTHORIZATION_CODE", "REFRESH_TOKEN" ] ],
  "supportedResponseTypes": [ "java.util.HashSet", [ "CODE", "TOKEN" ] ],
  "attributeReleasePolicy" : {
    "@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
  }
}

As you can see, clientSecret is missing from the service definition.

CAS does not support both clientSecret and PKCE on the OIDC service at the same time.

Testing

  1. First, generate code_verifier and code_challenge

    #!/bin/bash
    
    random=$(xxd -c 32 -u -l 32 -p /dev/urandom)
    code_verifier=$(echo -n $random | xxd -r -p  | base64 | tr '+' '-' | tr '/' '_' | tr -d '=')
    code_challenge=$(echo -n $code_verifier | openssl dgst -binary -sha256  | base64 | tr '+' '-' | tr '/' '_' | tr -d '=')
    echo $code_verifier
    echo $code_challenge
  2. Paste following line into your browser. Adjust values according to your deployment and do not forget to include generated code_challenge.

    https://iam-appliance.tld/cas/oidc/authorize?response_type=code&code_challenge=Whubzdv9zyTyeqdpEpouWE1QVQ0tGlMpbn3eJpTuHog&code_challenge_method=S256&client_id=vueappjs&redirect_uri=http://testjs.example.com:8080/auth/signinwin/main&scope=openid&state=789456
  3. If you are not already logged in, CAS displays a login screen. Log in with your test user.

  4. After successful login, CAS issues the code and redirects you to the SP. The URL will look like this:

    http://testjs.example.com:8080/auth/signinwin/main?code=OC-1-151iQqGjQW3em0UWQ-Zvp9iChhxIc&state=789456
  5. Now, copy the value of code parameter. Validity of code is time-limited to about 30 seconds so you have to be quick.

  6. And paste the code and code_verifier into the token request…​

    curl -k -XPOST -H 'Origin: http://testjs.example.com:8080' --data "code_verifier=JkMRw1qO7jjucdmuQGTdsmDEGivltSJU4qs01GFa4aU&grant_type=authorization_code&redirect_uri=http://testjs.example.com:8080/auth/signinwin/main&client_id=vueappjs&scope=openid&code=OC-1-eRHH8zUs-qesawmcDECSZEEmf0sl6drQ" "https://iam-appliance.tld/cas/oidc/token"
  7. …​and obtain the access token and OIDC id_token.

    {
      "access_token":"AT-1-j3EPvr3jfHIVXEPnn4M8L-vMzg5L",
      "id_token":"eyJhbG... abbreviated ...8x8nAs6CaojdCw1YkFTv3qQu5VYCos_kn_f8uoHzCVkA",
      "token_type":"bearer",
      "expires_in":28800,
      "scope":"openid"
    }
  8. Finally, use the access_token to obtain user profile information. The response will contain attributes from all scopes the access token was issued for.

    curl -k -XGET --header "Authorization: Bearer AT-1-j3EPvr3jfHIVXEPnn4M8L-vMzg5L" https://iam.appliance.tld/cas/oidc/profile
  9. User profile is returned.

    {
      "sub":"idmtestuser",
      "service":"http://testjs.example.com:8080",
      "auth_time":1670487660,
      "id":"idmtestuser",
      "client_id":"vueappjs",
      "attributes":{
        ... user attributes listed here ...
      }
    }