100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Implementing OAuth in a Web App

A practical guide to wiring the Authorization Code flow into a server-rendered web application, from callback handling to secure token storage and refresh rotation.

Practical OAuthIntermediate10 min readJul 10, 2026
Analogies

Authorization Code Flow for Server-Rendered Apps

A traditional server-rendered web application is a confidential client: it can hold a client_secret safely on the server, so it uses the standard Authorization Code flow. The browser is redirected to the provider's /authorize endpoint with client_id, redirect_uri, scope, and state; after the user consents, the provider redirects back to a registered callback URL with a short-lived authorization code, which the server then exchanges directly, server-to-server, at the /token endpoint using its client_secret.

🏏

Cricket analogy: The redirect to /authorize is like a fan being sent from the stadium turnstile to a separate ticket-verification booth, and the callback with a code is like the booth handing back a stub that only the stadium's own back office can redeem for stand access.

Session Management and Token Storage

Because the code exchange happens server-side, the resulting access_token and refresh_token should stay on the server, held in a session store such as Redis or an encrypted database record, keyed by an opaque session identifier. The browser only ever receives an httpOnly, Secure, SameSite cookie containing that session ID, never the OAuth tokens themselves, which keeps the tokens out of reach of any JavaScript running on the page, including malicious injected scripts.

🏏

Cricket analogy: It's like the team management keeping player contracts locked in the franchise office (server-side session store), while each player only carries a numbered access card (session cookie) that means nothing outside the stadium's own systems.

javascript
// Express.js OAuth callback handler (server-rendered web app)
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;

  if (state !== req.session.oauthState) {
    return res.status(400).send('Invalid state parameter');
  }

  const tokenResponse = await fetch('https://idp.example.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: 'https://app.example.com/callback',
      client_id: process.env.OAUTH_CLIENT_ID,
      client_secret: process.env.OAUTH_CLIENT_SECRET,
    }),
  });

  const tokens = await tokenResponse.json();

  // Store tokens server-side; browser only ever gets a session cookie
  req.session.accessToken = tokens.access_token;
  req.session.refreshToken = tokens.refresh_token;
  req.session.tokenExpiry = Date.now() + tokens.expires_in * 1000;

  res.redirect('/dashboard');
});

State Parameter and CSRF Protection

The state parameter is a random, unguessable value the server generates and stores in the user's pre-authentication session before redirecting to /authorize; when the provider redirects back, the server compares the returned state against the stored value to confirm this callback corresponds to a request it actually initiated, blocking login CSRF attacks where an attacker tricks a victim into completing someone else's OAuth flow. Even for confidential server-side clients, OAuth 2.1 and current best practice recommend adding PKCE as a defense-in-depth measure against authorization code injection.

🏏

Cricket analogy: state is like a match referee issuing a unique toss-coin serial number before the game and checking it against the post-match report — without matching serials, you can't be sure the result being reported belongs to that specific toss.

Prefer a well-maintained OAuth/OIDC client library — such as openid-client, Passport.js strategies, or your framework's built-in OAuth middleware — over hand-rolling code exchange, state validation, and token refresh logic. These libraries correctly handle edge cases like clock skew, PKCE generation, and token validation that are easy to get subtly wrong by hand.

Refresh Token Rotation

Access tokens should be short-lived, often 5 to 15 minutes, to limit the damage if one leaks, while the longer-lived refresh_token is used to silently obtain new access tokens without re-prompting the user. Under refresh token rotation, the authorization server issues a brand-new refresh_token on every use and invalidates the old one; if an already-used refresh_token is presented again, the server treats it as a signal of theft and revokes the entire token family, forcing re-authentication.

🏏

Cricket analogy: It's like a bowler getting a fresh new ball every set number of overs rather than using one ball for the entire innings — and if the old ball mysteriously reappears in play, the umpires immediately suspect tampering and stop play.

Never store OAuth tokens in localStorage, sessionStorage, or a client-readable cookie in a server-rendered app — the whole point of keeping this a confidential, server-side flow is that tokens never reach the browser at all. If you find yourself passing an access_token into a client-side script, you've likely broken the security model this architecture is built on.

  • Server-rendered apps are confidential clients and should use the standard Authorization Code flow with a client_secret.
  • The authorization code is exchanged server-to-server at the /token endpoint, never in the browser.
  • Tokens belong in server-side session storage (e.g., Redis); the browser should only hold an httpOnly session cookie.
  • The state parameter must be validated on callback to prevent login CSRF attacks.
  • Even confidential clients benefit from adding PKCE as defense-in-depth per OAuth 2.1 guidance.
  • Refresh token rotation issues a new refresh_token on each use and revokes the token family if reuse is detected.
  • Prefer established OAuth client libraries over hand-rolled implementations to avoid subtle security bugs.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#OAuth20StudyNotes#ImplementingOAuthInAWebApp#Implementing#OAuth#Web#App#WebDevelopment#StudyNotes#SkillVeris