Course contentsShow
← Node.js
Lesson 1832 of 1,98016. Security Hardening and Threat MitigationFree lesson

Hardening Authentication Flows with OAuth2

Properly implement state validation and authorization code exchange to prevent token theft.

In this lesson, you will learn how to prevent authorization code interception and CSRF attacks by strictly validating the state parameter and ensuring secure code exchange.

When a user initiates an OAuth2 flow, your Node.js server redirects them to the identity provider. To prevent Cross-Site Request Forgery (CSRF), you must generate a cryptographically strong, unique state value before the redirect and store it in an encrypted, HTTP-only session cookie.

When the identity provider redirects the user back to your /callback endpoint, it includes both an authorization_code and the state value. If the state returned by the provider does not exactly match the value stored in your user's session, the request must be rejected immediately. This ensures that the response was triggered by your server, not an attacker attempting to inject their own authorization code into your user's session.

After validating the state, you must exchange the authorization_code for an access token. A common vulnerability occurs when developers perform this exchange insecurely or expose the code to logs or client-side scripts.

// Example: Validating state and exchanging the code
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  // 1. Validate state against the session
  if (!state || state !== req.session.oauthState) {
    return res.status(403).send('Invalid state: Potential CSRF attack.');
  }

  // 2. Clear state to prevent reuse
  delete req.session.oauthState;

  // 3. Exchange code for token server-side (never expose the code)
  const tokenResponse = await axios.post(TOKEN_ENDPOINT, {
    code,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET, // Keep this strictly on the backend
    grant_type: 'authorization_code',
    redirect_uri: REDIRECT_URI
  });

  // Handle token response...
});

Crucially, the exchange must happen server-to-server. Your Node.js application should use its client_secret to authenticate the request to the token endpoint. By keeping the client_secret out of your frontend bundle and ensuring the authorization_code is never leaked, you neutralize the risk of an attacker masquerading as your application to request tokens on behalf of your users.

Key Takeaway: Always pair a cryptographically unique state parameter with server-side code exchange to eliminate CSRF vulnerabilities and ensure the integrity of the OAuth2 handshake.