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_verifieris 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_challengeis derived from thecode_verifierusing one of the two possible transformations:plainandS256. -
The
code_challenge_methodtells the server which function was used to transform thecode_verifier(plain or S256). It defaults toplainif not explicitly set.
See RFC 7636 for complete specifications.
CAS OIDC service 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
-
First, generate
code_verifierandcode_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 -
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
-
If you are not already logged in, CAS displays a login screen. Log in with your test user.
-
After successful login, CAS issues the
codeand 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
-
Now, copy the value of
codeparameter. Validity ofcodeis time-limited to about 30 seconds so you have to be quick. -
And paste the
codeandcode_verifierinto 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"
-
…and obtain the
access tokenand OIDCid_token.{ "access_token":"AT-1-j3EPvr3jfHIVXEPnn4M8L-vMzg5L", "id_token":"eyJhbG... abbreviated ...8x8nAs6CaojdCw1YkFTv3qQu5VYCos_kn_f8uoHzCVkA", "token_type":"bearer", "expires_in":28800, "scope":"openid" } -
Finally, use the
access_tokento 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
-
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 ... } }