Understanding OAuth 2.0 for Web Developers


OAuth 2.0 is the standard protocol for authorisation on the web. Almost every “Sign in with Google/GitHub/Apple” button you’ve clicked uses it. Despite being ubiquitous, it’s frequently misunderstood and misimplemented. This guide explains OAuth 2.0 in practical terms.

What OAuth 2.0 Actually Does

OAuth 2.0 lets a user grant a third-party application limited access to their resources on another service, without sharing their password. When you sign in to a website using your Google account, you’re telling Google: “Let this website access my basic profile information.” The website never sees your Google password.

The protocol defines four roles:

  • Resource Owner: The user whose data is being accessed
  • Client: The application requesting access (your app)
  • Authorization Server: The service that authenticates the user and issues tokens (e.g., Google’s auth server)
  • Resource Server: The service that hosts the user’s data (e.g., Google’s API)

The Authorization Code Flow

This is the flow you should use for web applications. Here’s what happens step by step:

  1. Your app redirects the user to the authorisation server with specific parameters:
https://accounts.google.com/o/oauth2/v2/auth?
  client_id=YOUR_CLIENT_ID&
  redirect_uri=https://yourapp.com/callback&
  response_type=code&
  scope=openid email profile&
  state=random_csrf_token
  1. The user logs in to Google (if not already) and approves the requested permissions.

  2. Google redirects back to your app with an authorisation code:

https://yourapp.com/callback?code=AUTH_CODE&state=random_csrf_token
  1. Your server exchanges the code for tokens by making a server-to-server request:
const response = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    code: authCode,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    redirect_uri: 'https://yourapp.com/callback',
    grant_type: 'authorization_code',
  }),
});

const { access_token, refresh_token, id_token } = await response.json();
  1. Your server uses the access token to call the resource server’s API.

The critical security property: the client secret never leaves your server. The authorisation code alone is useless without it.

PKCE: The Required Extension

Authorization Code with PKCE (Proof Key for Code Exchange) should be used for all clients, including confidential (server-side) ones. PKCE prevents authorisation code interception attacks.

Before starting the flow, your app generates a random code_verifier and derives a code_challenge from it:

const codeVerifier = generateRandomString(64);
const codeChallenge = base64url(sha256(codeVerifier));

The code_challenge is sent in the initial authorisation request. The code_verifier is sent when exchanging the code for tokens. The authorisation server verifies that they match, ensuring the entity exchanging the code is the same one that initiated the flow.

Token Types

OAuth 2.0 involves multiple token types, and confusing them causes security issues:

Access tokens grant access to the resource server’s API. They’re short-lived (minutes to hours) and should be treated as opaque strings by the client.

Refresh tokens are used to obtain new access tokens when the current one expires. They’re long-lived and must be stored securely. If a refresh token is compromised, the attacker can generate access tokens indefinitely until the refresh token is revoked.

ID tokens (from OpenID Connect, which layers on top of OAuth 2.0) contain information about the authenticated user. They’re JWTs that can be decoded to extract user profile data.

Common Mistakes

Not validating the state parameter. The state parameter prevents CSRF attacks. Generate a random value, store it in the user’s session before redirecting, and verify it matches when the callback arrives. Skipping this check is a vulnerability.

Storing tokens in localStorage. Access tokens and especially refresh tokens should not be stored in browser localStorage or sessionStorage. These are accessible to any JavaScript running on the page, including injected scripts from XSS attacks. Use httpOnly cookies for token storage.

Using the Implicit Flow. The Implicit Flow (returning tokens directly in the URL fragment) is deprecated. It exposes tokens in browser history, referrer headers, and server logs. Always use the Authorization Code flow with PKCE.

Requesting too many scopes. Only request the permissions your application actually needs. Requesting read:email when you also want calendar access, contact lists, and file storage will rightfully scare users and reduce conversion rates.

Not handling token expiration. Access tokens expire. Your application needs to detect 401 responses, use the refresh token to obtain a new access token, and retry the original request. Libraries like axios support interceptors that handle this transparently.

OpenID Connect

OpenID Connect (OIDC) is an identity layer on top of OAuth 2.0. While OAuth 2.0 is about authorisation (accessing resources), OIDC adds authentication (proving who the user is).

The practical difference: with plain OAuth 2.0, you get an access token. With OIDC, you also get an ID token containing the user’s identity claims (name, email, profile picture). If you’re implementing “Sign in with…” functionality, you want OIDC, not raw OAuth 2.0.

Library Recommendations

Don’t implement OAuth 2.0 from scratch. The protocol has subtle security requirements that are easy to miss. Use established libraries:

  • Node.js: passport with provider-specific strategies, or arctic for a lighter approach
  • Next.js: Auth.js (formerly NextAuth)
  • Python: authlib or python-social-auth
  • Go: golang.org/x/oauth2

These libraries handle state generation, PKCE, token exchange, and session management correctly. The time you save avoiding security bugs far exceeds the time spent learning the library.

Wrapping Up

OAuth 2.0 is more complex than it appears from the user’s perspective. A “Sign in with Google” button hides a multi-step protocol with significant security implications. Understanding the flow, using PKCE, validating state, storing tokens securely, and using established libraries will keep your implementation sound.

When in doubt, follow the current best practices from the OAuth 2.0 Security Best Current Practice RFC. It’s the definitive reference for secure OAuth 2.0 implementation.