Welcome to the third chapter of our OAuth tutorial!
In the previous chapter, we built the Local Callback Listener. It successfully "caught" the user returning from the web browser with a temporary Authorization Code.
However, having that code is like having a "claim check" for a coat check room. You can't wear the claim check. You need to exchange it for the actual coat (the Access Token).
In this chapter, we will build the API Client.
The Authorization Server (the entity that issues logins) is strict. It requires requests to be formatted perfectly.
POST requests.application/x-www-form-urlencoded or json.If you get one character wrong, the server rejects you. We don't want to write raw HTTP requests every time we need to log in.
Think of the API Client as a specialized lawyer or travel agent.
The Orchestrator (from Chapter 1) has the code. It needs to call a function to get the tokens.
Our Goal: We want a simple function that looks like this:
// We have the code from the Listener
const code = "captured-auth-code-123";
// We simply ask the client to do the heavy lifting
const tokens = await exchangeCodeForTokens(
code,
state,
verifier,
port
);
console.log(tokens.accessToken); // "ey..." (The real key!)
To manage this, our Client handles three distinct operations.
Before the user even leaves the terminal, we need to generate the link they will click. This isn't just google.com. It is a long URL containing the permissions we want (Scopes), our ID (Client ID), and security codes.
This is the core purpose of this chapter. We send the Authorization Code + PKCE Verifier to the server. The server checks them. If they match, it sends back the Access Token.
Access Tokens are short-lived (often 1 hour) for security. Refresh Tokens are long-lived (days or months). When the Access Token expires, the Client automatically uses the Refresh Token to get a new one.
Let's visualize the "Trade" process.
Let's look at src/oauth/client.ts. We use a library called axios to make the actual HTTP network requests.
Instead of pasting strings together (which is error-prone), we use the JavaScript URL object. This ensures special characters are handled correctly.
export function buildAuthUrl({ codeChallenge, state, port }): string {
// Start with the base Authorization URL
const authUrl = new URL(getOauthConfig().CONSOLE_AUTHORIZE_URL)
// Append necessary parameters safely
authUrl.searchParams.append('client_id', getOauthConfig().CLIENT_ID)
authUrl.searchParams.append('response_type', 'code')
// Tell the server where to send the user back (Our Local Listener!)
authUrl.searchParams.append('redirect_uri', `http://localhost:${port}/callback`)
// Attach security parameters (See Chapter 5)
authUrl.searchParams.append('code_challenge', codeChallenge)
authUrl.searchParams.append('state', state)
return authUrl.toString()
}
This is the "Trade." We perform a POST request. Notice we include the code_verifier. This is the proof that we are the same person who started the flow.
Note: We will explain code_verifier in depth in PKCE Security (Crypto).
export async function exchangeCodeForTokens(
authorizationCode: string,
state: string,
codeVerifier: string,
port: number,
): Promise<OAuthTokenExchangeResponse> {
// 1. Prepare the form data
const requestBody = {
grant_type: 'authorization_code', // The type of trade we are doing
code: authorizationCode, // The claim check
code_verifier: codeVerifier, // The security proof
redirect_uri: `http://localhost:${port}/callback`,
client_id: getOauthConfig().CLIENT_ID,
}
// 2. Send the request
const response = await axios.post(getOauthConfig().TOKEN_URL, requestBody)
// 3. Return the data (contains access_token and refresh_token)
return response.data
}
When the application has been running for a while, the token might expire. The Client handles this renewal.
This looks very similar to the exchange above, but the grant_type changes.
export async function refreshOAuthToken(refreshToken: string): Promise<OAuthTokens> {
const requestBody = {
grant_type: 'refresh_token', // Different trade type!
refresh_token: refreshToken, // The long-lived renewal card
client_id: getOauthConfig().CLIENT_ID,
}
// Send request to the same URL
const response = await axios.post(getOauthConfig().TOKEN_URL, requestBody)
// Extract new tokens
const data = response.data
return {
accessToken: data.access_token,
// If server didn't send a new refresh token, keep the old one
refreshToken: data.refresh_token || refreshToken,
expiresAt: Date.now() + (data.expires_in * 1000)
}
}
Once we have the accessToken, we can use it to fetch information about the user (like their email or billing status). The token acts as our key to the API.
// Helper to get user info
export async function fetchProfileInfo(accessToken: string) {
// We attach the token in the "Authorization" header
// It looks like: "Bearer eyJhbGci..."
const response = await axios.get(getOauthConfig().PROFILE_URL, {
headers: { Authorization: `Bearer ${accessToken}` },
})
return response.data;
}
The API Client abstracts away the messy details of HTTP.
At this point in the tutorial, we have:
Now we have a raw Access Token. But who does this token belong to? Is it a free user or a paid user? What is their email?
In the next chapter, we will learn how to resolve the identity of the user.
Next Chapter: Profile & Identity Resolution
Generated by Code IQ