13.6 Generating a random code verifier and challenge
Before we go any further, we should take a look at the PKCE code verifier and code challenge.
Up to this point, we have been using a static code verifier and corresponding code challenge. This has let us get up and running, but the point of the PKCE system is to allow the authentication server to verify that the system that passed the request for the authorization code is the same system that is subsequently requesting the access token. To implement this, we must use a fresh, random code verifier each time.
For more information about PKCE code verifiers and code challenges, see section 4.2.1, Generating a PKCE code verifier and code challenge.
Edit the index.html file you created in the previous example (see section 13.5, Obtaining an access token).
In the list of constants, delete the following lines:
// Code challenge
const code_challenge = "lzKaVv4bWu06z_m0yFynJj6zttnU5gYpXah8tLYKzGg";
// Code verifier
const code_verifier = "TiGVEDHIRkdTpif4zLw8v6tcdG2VJXvP4r0fuLhsXIj";
In the createForm() function, edit the line that creates the code_challenge_element of the form; change:
var code_challenge_element = createFormField("hidden", "code_challenge", code_challenge);
to:
var code_challenge_element = createChallengeField();
At the end of the <script> block, add the functions that you are going to use to create the code verifier and code challenge.
First, create a function to generate a random string:
// Create a function to generate a random code verifier string
function generateRandomString(length) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
This function creates a string of the specified length using the characters A-Z, a-z, and 0-9. You are going to use this to create the code verifier.
Next, create a function that generates a SHA-256 hash of the random string.
// Create an asynchronous function to generate a code challenge from the code verifier
async function generateCodeChallenge(code_verifier) {
var digest = await crypto.subtle.digest("SHA-256",
new TextEncoder().encode(code_verifier));
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
This function takes the random code verifier, then creates a hash that you can use as the code challenge.
Next, create a function that uses the two functions above to create a code verifier and code challenge, which is then added to the code challenge on the request form.
function createChallengeField() {
// Create the code challenge element for the form
var code_challenge_element = document.createElement("input");
code_challenge_element.setAttribute("type", "hidden");
code_challenge_element.setAttribute("name", "code_challenge");
// Create a random string to use as the code verifier
var code_verifier = generateRandomString(128);
// Store the code verifier to use later
window.sessionStorage.setItem("code_verifier", code_verifier);
// Generate the code challenge from the asyncronous function
(async () => {
const code_challenge = await generateCodeChallenge(code_verifier);
code_challenge_element.setAttribute("value", code_challenge);
})();
// Return the code challenge form element
return code_challenge_element;
}
Notice how the function stores the code verifier in the window session storage so we can use it later when requesting the access token.
Finally, edit the existing getAccessToken() function to retrieve the code verifier from the window session storage, and pass it to the token endpoint:
function getAccessToken() {
// Use the authorization code to obtain an access code
// This retrieves the code verifier from the browser's session storage
request.open("POST", server + token_url, true);
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
request.send('client_id=' + client_id +
// Obtain the code_verifier from the session storage
'&code_verifier=' + window.sessionStorage.getItem("code_verifier") +
'&grant_type=authorization_code&redirect_uri=' + redirect_uri +
'&code=' + code);
document.getElementById("login").innerHTML="Working...";
}
Try it out. It should work in exactly the same way as the previous example, with the only difference being you are securely generating a random code verifier and deriving a corresponding code challenge each time. You can verify this by checking the URL passed to the web.oauth2 service, which contains the code challenge:
Now we can move on to the main event – calling the API.