In this tutorial, you will learn how to perform PKCE verification when acquiring an access token using the OAuth 2 Authorization Code Grant flow.
For video lessons on how to secure your Spring Boot application with OAuth 2.0. and Spring Security 5, please checkout my complete video course OAuth 2.0. in Spring Boot applications.
PKCE stands for Proof Key for Code Exchange and the PKCE-enhanced Authorization Code Flow builds upon the standard Authorization Code Flow, so the steps are very similar. To learn how to acquire an access token using the Authorization Code flow without the PKCE, please follow this tutorial: Keycloak: Authorization Code Grant Example.
This authorization flow is mostly used by Native apps and it provides an additional level of security for public OAuth clients.
Configure PKCE in the Authorization Server
For this tutorial, I am going to use the Keycloak authorization server and to use the PKCE-enhanced Authorization Code flow, the OAuth 2 Client, in Keycloak, needs to have an additional configuration.
- The OAuth Client application needs to be Public and the Standard flow needs to be enabled.
- While still on the Client configuration page in Keyloak, scroll down the page and expand the Advanced Settings section. For the Proof Key for Code Exchange Code Challenge Method option, select S256.
Now, Keycloak is ready to support the PKCE-enhanced Authorization Code Flow.
The Request for Authorization Code
The request URL in the PKCE-enhanced Authorization Code Flow differs from the request URL of the standard Authorization Code Grant flow in that it accepts additional two request parameters: the code_challenge and the code_challenge_method.
http://localhost:8080/auth/realms/appsdeveloperblog/protocol/openid-connect/auth ?client_id=photo-app-pkce &response_type=code &scope=profile openid &redirect_uri=http://localhost:8083/callback &state=h4u8fF2okGBio38uE &code_challenge=-sUEoAV-txYvhniiuJ4-gwNCtsiD2XiIPvLQYm-sUsE &code_challenge_method=S256
Where:
- client_id – REQUIRED. This is an OAuth client identifier. A client application that communicates with an authorization server needs to first register itself with the authorization server and acquire a client_id and a client_secret. For this request to work, providing client_id is sufficient,
- response_type – REQUIRED. This request parameter is required and its value MUST be set to “code”,
- scope – OPTIONAL. The scope of the access request. This value will be different for your application depending on the scopes you have configured in the authorization server.
- redirect_uri – OPTIONAL. This value is a URL to a page in your application that will handle the response from an authorization server. If this request is valid and is successful, an authorization server will generate an access token and it will redirect the user to a URI provided in this request parameter. The access token will be attached to this redirect URI as a query string parameter. If the request_uri is included in the request, its value must much the request URI value registered with an authorization server. Otherwise, the request will not be successful.
- state – RECOMMENDED. This is an opaque value used by the client to maintain state between the request and callback. Your client application will need to generate this random alphanumeric string of characters and include it in the request. The authorization server will attach this value to a redirect_uri as a query string parameter. This “state” parameter SHOULD be used for preventing cross-site request forgery. The page that handles the response from the authorization server will need to read this value and compare it to the original one that was sent with an initial request. The two values must match,
- code_challenge – is a Base64 URL Encoded value. For details on how to generate the code_challenge value, please check this page: “Client Creates Code Verifier“,
- code_challenge_method – This is the value we have configured in the Keyclock for the OAuth Client above. This value should be “S256“.
Response
If the request for the PKCE-enhanced Authorization Code is successful, the authorization server will redirect Client to a redirect_url provided in the request and will attach to this redirect URL an authorization code. The authorization code will need to be used in the following request to exchange it for the access token.
Here is how the redirect URL with an access token attached looks like:
http://localhost:8083/callback ?state=h4u8fF2okGBio38uE &session_state=20af20a8-7146-4a97-a636-a59c784ad59b &code=b06c44f3-71be-4525-81f8-9c88472154c6.20af20a8-7146-4a97-a636-a59c784ad59b.0c9b74af-f0cb-48b8-9762-1bd23841c73a
Exchange Authorization Code for Access Token
Now when we have an authorization code we can exchange it for an access token. Because we are using a PKCE-enhanced Authorization Code Flow, a request for an access token will need to contain a code_verifier parameter which value is a Base64 URL encoded value of code_verifier value.
curl --location --request POST 'http://localhost:8080/auth/realms/appsdeveloperblog/protocol/openid-connect/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'client_id=photo-app-pkce' \ --data-urlencode 'code=b06c44f3-71be-4525-81f8-9c88472154c6.20af20a8-7146-4a97-a636-a59c784ad59b.0c9b74af-f0cb-48b8-9762-1bd23841c73a' \ --data-urlencode 'redirect_uri=http://localhost:8083/callback' \ --data-urlencode 'code_verifier=c3cxd2UzNHJmZGUzNHJneWh1NzhpazFxd2U0cmZkZXI1Nnl1N3lnZnJ0NmpraW85NHJkc3dlcg'
Where:
- grant_type – REQUIRED. The grant_type is a required parameter and its value must be “authorization_code”. If you provide a different value here, the request will not be successful,
- code – REQUIRED. The value of a code request parameter must be an OAuth Authorization Code that was received from an authorization server. It is this value we are exchanging for an access token,
- code_verifier – REQUIRED. Because to acquire an authorization code we have used code_challenge, in this request, we will need to provide a value of code_verifier. It is a Base64 URL encoded value that was used to produce the code_challenge. To learn how to produce code_verifier value, please have a look at this page “Client Creates Code Verifier“.
If the request is successful, then authorization server will respond with an access token.
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItNUlsX2I0cUktdWFvaEI3d244UHY3WEM2UEktU3BNbmZCRnlJZUx6QTJNIn0.eyJleHAiOjE1OTMwMzgzODIsImlhdCI6MTU5MzAzODA4MiwiYXV0aF90aW1lIjoxNTkzMDM3ODMxLCJqdGkiOiJmMDIyMjk2Zi03YWU3LTRiOWEtOTE0ZS1kZDJiNTFlOGQ5OWIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvYXBwc2RldmVsb3BlcmJsb2ciLCJzdWIiOiIxZGRlM2ZjMy1jNmRiLTQ5ZmItOWIzZC03OTY0YzVjMDY4N2EiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJwaG90by1hcHAtcGtjZSIsInNlc3Npb25fc3RhdGUiOiIwMWFmZGFhNS00ZTViLTQ4YmYtYjg0MS0wMGQyZDhjMGQ0Y2UiLCJhY3IiOiIwIiwic2NvcGUiOiJvcGVuaWQifQ.KpfpcOX8uO93fn38oL-huHeAbjS9ObtipPMFtx9u3k66QS6cNeRhhfnTxHci1OrbVxJAyuzwBVRMt0Tr-UIcZKJrsPFts3TeQzQKRzAiNAypsAegppRe9w9gRijp884zD3N_vTPi_qss7yaz6JFQUD2MvlZLST14Ye75xDJlvhtgSNmfJrGFNkZYS4XKoxj5_zgYuTd71eopL4-Xl6I-GVXuwa70Ie6IeImMwUMB0nIx9Xiyp-BiDXkJOXq0_9vURwLP2ScCGALeHThziEGPb5Z9P5O3zsQ36nlnWSqrZt5VHIObfIu0hwe-UkRaIK-rU_76wma0oLhYrNlz7UKXPw", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJlYWQyMDZmOS05MzczLTQ1OTAtOGQ4OC03YWNkYmZjYTU5MmMifQ.eyJleHAiOjE1OTMwMzk4ODIsImlhdCI6MTU5MzAzODA4MiwianRpIjoiZDM2MjU2ZGMtMzlhYi00MmM4LWI5OWEtYTkxMDJjMGYzMzIzIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2FwcHNkZXZlbG9wZXJibG9nIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2FwcHNkZXZlbG9wZXJibG9nIiwic3ViIjoiMWRkZTNmYzMtYzZkYi00OWZiLTliM2QtNzk2NGM1YzA2ODdhIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InBob3RvLWFwcC1wa2NlIiwic2Vzc2lvbl9zdGF0ZSI6IjAxYWZkYWE1LTRlNWItNDhiZi1iODQxLTAwZDJkOGMwZDRjZSIsInNjb3BlIjoib3BlbmlkIn0.Ynu_LX0QRPDb4i-ggdaAE_tpMyqo9gX__D15Lm59tbc", "token_type": "bearer", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItNUlsX2I0cUktdWFvaEI3d244UHY3WEM2UEktU3BNbmZCRnlJZUx6QTJNIn0.eyJleHAiOjE1OTMwMzgzODIsImlhdCI6MTU5MzAzODA4MiwiYXV0aF90aW1lIjoxNTkzMDM3ODMxLCJqdGkiOiI5YzdhMmQ0ZC0yZDlhLTRjN2UtODgxOS1mNzgxODE2NWY2NTUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvYXBwc2RldmVsb3BlcmJsb2ciLCJhdWQiOiJwaG90by1hcHAtcGtjZSIsInN1YiI6IjFkZGUzZmMzLWM2ZGItNDlmYi05YjNkLTc5NjRjNWMwNjg3YSIsInR5cCI6IklEIiwiYXpwIjoicGhvdG8tYXBwLXBrY2UiLCJzZXNzaW9uX3N0YXRlIjoiMDFhZmRhYTUtNGU1Yi00OGJmLWI4NDEtMDBkMmQ4YzBkNGNlIiwiYWNyIjoiMCJ9.LKG_lLWSoFADBnJB4DrQz0RdcaxqK_99fhGoQQqJP55RQ9kmsq3qGXih2aUOQwSpRjYBNaoPZfeHnBiyvBUfxh3Q7pRXVZ4QeeYZ1S-9AcIXvnghofNiuWxRbFQnPg9VbcbRLpKQIBECaJ3J4xZBz1WqsJVYLJNopD5tz8qyd8M8ZL3QUe52nBuLcL0EM94R4r2eMOzciZ0FLcmAU49WkeXVIlEBF0ZotWzc_59tlCR-OIFhiMpdzINjqH92KKeOmBkGoHpaFU-7kcB9WXG3pqEPzLcI6C_bN-45kEl7rWTRUBW4w3uYcVEn1WymuKYAx1ByJM3YnL34pGFd5Bpplw", "not-before-policy": 0, "session_state": "01afdaa5-4e5b-48bf-b841-00d2d8c0d4ce", "scope": "openid" }
I hope this tutorial was helpful to you. To learn how to use Keycloak with other OAuth 2 Authorization flows, please check the following tutorials:
Happy learning 🙋🏻♂️
That cleared the PKCE matter, but I’m a little confused how to apply those two extra params in the Keycloak Js Adapter. Do you know how to do it?