Welcome to the final chapter of our OAuth tutorial!
In the previous chapter, Profile & Identity Resolution, we learned how to turn a cryptic access token into meaningful user information, like a name or email address.
We now have a fully functional login system. However, we have glossed over one critical detail mentioned all the way back in OAuth Flow Orchestrator: Security.
Because our application runs on your computer (and not on a secure server in a data center), we cannot hide a "Client Secret" inside the code. Anyone could read the source code and steal it.
So, how do we prove to the server that we are who we say we are, without a permanent password?
In this chapter, we will build the PKCE Security module (Proof Key for Code Exchange).
To understand PKCE (pronounced "Pixy"), imagine checking your luggage at a hotel.
If a thief overheard you checking your bag, they might know your name, but they don't have the ticket stub in your pocket. They cannot claim your bag.
We need to generate a "Ticket Stub" (Secret) and a public description of it every time a user tries to log in.
Our Goal: We want a utility that provides these cryptographic values:
// 1. Create the secret ticket (The Verifier)
const verifier = generateCodeVerifier();
// Output: "43_byte_random_string_secret..."
// 2. Create the public description (The Challenge)
const challenge = generateCodeChallenge(verifier);
// Output: "Scrambled_Hash_Of_Verifier..."
// 3. Create a random ID for the session (State)
const state = generateState();
// Output: "Another_Random_String..."
These values are passed to the OAuth Flow Orchestrator to start the journey.
There are three main terms you will see in the implementation.
This is a cryptographically random string. It is created before the login starts.
This is a "hashed" version of the Verifier. We send this to the Authorization Server immediately.
This is another random string used to prevent "CSRF" attacks. It ensures that the response coming back from the browser is actually for the request you sent, not a fake request injected by a hacker.
Here is the security dance that happens behind the scenes.
Let's implement this in src/oauth/crypto.ts. We rely on the built-in Node.js crypto library.
Standard encryption produces weird characters like +, /, and =. These break web URLs. We need a helper function to make our random strings "URL Safe."
import { createHash, randomBytes } from 'crypto'
function base64URLEncode(buffer: Buffer): string {
return buffer
.toString('base64') // Convert binary to text
.replace(/\+/g, '-') // Swap + for -
.replace(/\//g, '_') // Swap / for _
.replace(/=/g, '') // Remove padding =
}
The Verifier is just a random string. We use randomBytes(32) to get 32 bytes of pure noise, then convert it to text.
export function generateCodeVerifier(): string {
// Generate 32 bytes of random data
const randomData = randomBytes(32)
// Make it safe for URLs
return base64URLEncode(randomData)
}
Explanation: If you ran this twice, the odds of getting the same string are astronomically low. This makes it impossible for a hacker to guess.
This is the "One-Way Street." We use SHA-256, a mathematical algorithm that scrambles data.
export function generateCodeChallenge(verifier: string): string {
// Create a SHA-256 hasher
const hash = createHash('sha256')
// Feed it our secret verifier
hash.update(verifier)
// Return the scrambled result (encoded safely)
return base64URLEncode(hash.digest())
}
The state parameter is essentially the same as a verifierβjust a random ID tag for this specific login attempt.
export function generateState(): string {
// Just another random string!
return base64URLEncode(randomBytes(32))
}
Now you can see how this connects to the rest of the application:
generateCodeVerifier and saves it in memory.generateCodeChallenge and sends it to the URL Builder in the Client (Chapter 3).verifier to the Client.verifier to the API to prove ownership.Congratulations! You have completed the OAuth project tutorial.
By breaking down this complex security protocol into small, manageable pieces, we have built a robust login system:
You now have a production-ready authentication flow suitable for CLI tools and desktop applications!
Generated by Code IQ