Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PKCE Challenges and Verifiers

PKCE Code Challenges and Verifiers are used for preventing Cross-Site Request Forgery and authorization code injection attacks. Learn more about them here.

An example of writing code for PKCE in Rust follows.

This code can be edited and run in the browser as an aid in debugging client implementations. Learn how to use this tool here.

use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD};
use rand::{thread_rng, Rng};
use regex::Regex;
use sha2::{Digest, Sha256};

// This code is demonstrative, not performant.

// These are the characters that can be accepted as part of
// a code verifier.  They are being stored as a byte array.
const CHARS: &[u8] = b"\
ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
1234567890-._~";

/// Generate a code verifier of the given length.
///
/// # Panics
///
/// This code panics if the length is less than
/// 43 or greater than 128.
fn code_verifier(len: usize) -> String {
    assert!(len >= 43);
    assert!(len <= 128);

    // Use a cryptographically strong random number generator.
    let mut rng = thread_rng();

    (0..len) // for each in 0 to len exclusive
        .into_iter() // iterate
        .map(|_| rng.gen_range(0..CHARS.len())) // get an index into CHARS
        .map(|i| CHARS[i]) // get the byte at the index
        .map(char::from) // map it back to a character
        .collect() // collect the characters into a String
}

/// Generate a code challenge for the given verifier.
///
/// # Panics
///
/// This code panics if the given code verifier is less
/// than 43 characters long or greater than 128 characters.
fn code_challenge(code_verifier: String) -> String {
    assert!(code_verifier.len() >= 43);
    assert!(code_verifier.len() <= 128);

    code_verifier
        .chars() // get the characters in the String
        .for_each(|c| { // for each character
            assert!(CHARS.contains(&(c as u8))); // check that it is valid
        });

    // Prepare a SHA256 hash.
    let mut sha256 = Sha256::new();

    // Update the hash with the verifier.
    sha256.update(code_verifier);

    // Compute the final hash.
    let result = sha256.finalize();

    // Encode it as base64.
    let base64 = base64::engine::general_purpose::STANDARD.encode(&result[..]);

    // Make the base64 encoding "URL Safe" and remove the padding.
    base64
        .chars() // get the characters
        .filter_map(|c| match c {
            '=' => None,      // remove padding
            '+' => Some('-'), // make URL Safe

            '/' => Some('_'), // make URL Safe
            x => Some(x),     // return anything else untouched
        })
        .collect() // collect it into a String
}

/// Validate a code verifier and challenge pair.
/// The value `true` is returned when valid.
///
/// This is another way of writing the `code_challenge` function.
fn is_valid(code_verifier: String, code_challenge: String) -> bool {
    let re = Regex::new(r"^[A-Za-z0-9_\.\-\~]{43,128}$").unwrap();

    if !re.is_match(&code_verifier) {
        return false;
    }

    let digest = Sha256::digest(code_verifier.as_bytes());
    let encoded = BASE64_URL_SAFE_NO_PAD.encode(digest);

    // If the given verifier matches the challenge, the server
    // will accept the pair.
    encoded == code_challenge
}


fn main() {
    /// Prefer the maximum.
    let code_verifier = code_verifier(128);

    // Override it with your own by uncommenting the following
    // and replacing the contents of the string:
    // let code_verifier = "5-Giz4oGgbRTt2Q2VmhQMKw_aTp9UJCQuD_~ZAlP-QM".to_string();

    println!("code_verifier: {code_verifier}");

    let code_challenge = code_challenge(code_verifier.clone());

    // Override it with your own by uncommenting the following
    // and replacing the contents of the string:
    //let code_challenge = "-FG7uN-lx34GXN3xvKEPcwqoYnGX2R4ACX59z_X28vE".to_string();

    println!("code_challenge: {code_challenge}");

    if is_valid(code_verifier, code_challenge) {
        println!("the server will accept this!");
    } else {
        println!("something has gone wrong");
    }
}