Back to All Concepts
SecurityAuthenticationProtocolsAdvanced

OAuth 2.0 & OpenID Connect

Industry-standard protocols for authorization (delegated access) and authentication (identity verification), enabling secure "Sign in with Google/Facebook" and API access without sharing passwords.

What is OAuth 2.0?

OAuth 2.0 is an authorization framework that allows third-party applications to access user resources without exposing passwords.

Analogy: Instead of giving your hotel master key to the valet, you give them a valet key that only opens the parking garage.

The Problem: Password Sharing

Bad old way:
User → Gives Gmail password to Spotify
Spotify → Logs into Gmail as user
Result: Spotify has FULL access to Gmail forever

OAuth 2.0 way:
User → Authorizes Spotify to "Read Contacts" only
Google → Gives Spotify temporary token for contacts
Result: Spotify has LIMITED access, user can revoke anytime
Click to expand code...

Key Concepts

Roles

  1. Resource Owner: You (the user with data)
  2. Client: Third-party app (e.g., Spotify)
  3. Authorization Server: Issues tokens (e.g., accounts.google.com)
  4. Resource Server: Hosts protected data (e.g., Gmail API)

Tokens

Token TypePurposeLifetimeFormat
Access TokenAPI access1 hourOpaque string or JWT
Refresh TokenGet new access tokenDays/monthsOpaque string
ID Token (OIDC)User identityN/A (validate once)JWT

OAuth 2.0 Flows

1. Authorization Code Flow (Most Secure)

For server-side web apps with backend.

mermaid
sequenceDiagram
    participant User
    participant Client as Client App
    participant AuthServer as Authorization Server
    participant API as Resource Server
    
    User->>Client: Click "Sign in with Google"
    Client->>AuthServer: Redirect to /authorize
    AuthServer->>User: Show consent screen
    User->>AuthServer: Approve
    AuthServer->>Client: Redirect with code=ABC123
    Client->>AuthServer: POST /token (code + client_secret)
    AuthServer->>Client: access_token, refresh_token
    Client->>API: GET /user/profile (Bearer token)
    API->>Client: User data
Click to expand code...

Step-by-step:

javascript
// Step 1: Redirect user to authorization server
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `client_id=${CLIENT_ID}&` +
  `redirect_uri=${REDIRECT_URI}&` +
  `response_type=code&` +
  `scope=openid email profile&` +
  `state=random_string_to_prevent_csrf`;

res.redirect(authUrl);

// Step 2: User approves, Google redirects back with code
// GET /callback?code=ABC123&state=random_string_to_prevent_csrf

// Step 3: Exchange code for tokens (server-side)
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    code: code,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,  // SECRET! Never expose
    redirect_uri: REDIRECT_URI,
    grant_type: 'authorization_code'
  })
});

const { access_token, refresh_token, id_token } = await tokenResponse.json();

// Step 4: Use access token to call API
const userInfo = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
  headers: { 'Authorization': `Bearer ${access_token}` }
});
Click to expand code...

Security: Client secret never exposed to browser


2. Implicit Flow (Deprecated)

⚠️ Deprecated: Don't use this. Use Authorization Code + PKCE instead.

Old browser-only flow:
User approves → Redirect with access_token in URL fragment
Problem: Token exposed in browser history
Click to expand code...

3. Authorization Code + PKCE (For SPAs & Mobile)

PKCE (Proof Key for Code Exchange) makes OAuth secure for public clients (no client secret).

javascript
// Step 1: Generate code verifier and challenge
function generateCodeVerifier() {
  return base64urlencode(crypto.randomBytes(32));
}

function generateCodeChallenge(verifier) {
  return base64urlencode(sha256(verifier));
}

const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);

// Store verifier in sessionStorage
sessionStorage.setItem('pkce_verifier', codeVerifier);

// Step 2: Redirect to authorization with challenge
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `client_id=${CLIENT_ID}&` +
  `redirect_uri=${REDIRECT_URI}&` +
  `response_type=code&` +
  `scope=openid email&` +
  `code_challenge=${codeChallenge}&` +
  `code_challenge_method=S256`;

window.location.href = authUrl;

// Step 3: Exchange code for token with verifier
const verifier = sessionStorage.getItem('pkce_verifier');

const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  body: new URLSearchParams({
    code: code,
    client_id: CLIENT_ID,
    // NO client_secret needed!
    code_verifier: verifier,
    redirect_uri: REDIRECT_URI,
    grant_type: 'authorization_code'
  })
});
Click to expand code...

How PKCE prevents attacks:

Attacker intercepts authorization code
Attacker tries to exchange code
Server: "Give me code_verifier"
Attacker: "I don't have it" ❌
Legitimate client: "Here's the verifier" ✅
Click to expand code...

4. Client Credentials Flow (Machine-to-Machine)

For backend services with no user.

javascript
// Service-to-service authentication
const tokenResponse = await fetch('https://oauth.example.com/token', {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: SERVICE_ID,
    client_secret: SERVICE_SECRET,
    scope: 'api.read api.write'
  })
});

const { access_token } = await tokenResponse.json();

// Use token to call API
await fetch('https://api.example.com/data', {
  headers: { 'Authorization': `Bearer ${access_token}` }
});
Click to expand code...

Use case: Microservice A calling Microservice B


5. Refresh Token Flow

Get new access token without re-authentication.

javascript
// Access token expired (401 Unauthorized)
// Use refresh token to get new access token
const refreshResponse = await fetch('https://oauth2.googleapis.com/token', {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: STORED_REFRESH_TOKEN,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET
  })
});

const { access_token, refresh_token } = await refreshResponse.json();

// Store new tokens
if (refresh_token) {
  // Google rotates refresh tokens
  updateStoredRefreshToken(refresh_token);
}
Click to expand code...

OpenID Connect (OIDC)

OIDC = OAuth 2.0 + Authentication

It adds an ID Token (JWT) containing user identity.

ID Token Structure

javascript
// ID Token (JWT)
{
  "iss": "https://accounts.google.com",           // Issuer
  "sub": "110169484474386276334",                 // Subject (user ID)
  "aud": "your-client-id.apps.googleusercontent.com",  // Audience
  "exp": 1638360720,                              // Expiration
  "iat": 1638357120,                              // Issued at
  "email": "user@example.com",
  "email_verified": true,
  "name": "John Doe",
  "picture": "https://..."
}
Click to expand code...

Validating ID Token

javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

// Get Google's public keys
const client = jwksClient({
  jwksUri: 'https://www.googleapis.com/oauth2/v3/certs'
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    callback(null, key.publicKey || key.rsaPublicKey);
  });
}

// Verify ID token
jwt.verify(idToken, getKey, {
  audience: CLIENT_ID,
  issuer: 'https://accounts.google.com',
  algorithms: ['RS256']
}, (err, decoded) => {
  if (err) {
    console.error('Invalid token:', err);
  } else {
    console.log('User:', decoded);
  }
});
Click to expand code...

Critical validations:

  1. Signature: Verify with Google's public key
  2. Audience (aud): Must match your client_id
  3. Issuer (iss): Must be accounts.google.com
  4. Expiration (exp): Must be in future

Security Best Practices

1. Token Storage

javascript
// ❌ BAD: LocalStorage (vulnerable to XSS)
localStorage.setItem('access_token', token);

// ✅ GOOD: HttpOnly Cookie (not accessible to JavaScript)
res.cookie('access_token', token, {
  httpOnly: true,
  secure: true,        // HTTPS only
  sameSite: 'strict',  // CSRF protection
  maxAge: 3600000      // 1 hour
});
Click to expand code...

2. State Parameter (CSRF Protection)

javascript
// Generate random state
const state = crypto.randomBytes(16).toString('hex');
sessionStorage.setItem('oauth_state', state);

// Include in URL
const authUrl = `...&state=${state}`;

// Verify on callback
const returnedState = req.query.state;
const storedState = sessionStorage.getItem('oauth_state');

if (returnedState !== storedState) {
  throw new Error('CSRF attack detected!');
}
Click to expand code...

3. Scope Limitation

javascript
// ❌ BAD: Request all scopes
scope: 'https://www.googleapis.com/auth/gmail.modify'

// ✅ GOOD: Request minimum needed
scope: 'https://www.googleapis.com/auth/gmail.readonly'
Click to expand code...

4. Token Rotation

javascript
// Rotate refresh tokens on use
const { access_token, refresh_token } = await refreshTokens();

if (refresh_token) {
  // New refresh token issued, invalidate old one
  await db.updateRefreshToken(userId, refresh_token);
}
Click to expand code...

Real-World Implementations

Express.js Backend

javascript
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

const app = express();

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { secure: true, httpOnly: true }
}));

app.use(passport.initialize());
app.use(passport.session());

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: "https://example.com/auth/google/callback"
  },
  (accessToken, refreshToken, profile, done) => {
    // Save tokens and user to database
    User.findOrCreate({ googleId: profile.id }, {
      accessToken,
      refreshToken,
      profile
    }, (err, user) => {
      return done(err, user);
    });
  }
));

app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback', 
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/dashboard');
  }
);
Click to expand code...

React SPA with PKCE

javascript
// useAuth.js
import { useEffect, useState } from 'react';

export function useAuth() {
  const [user, setUser] = useState(null);
  
  async function login() {
    // Generate PKCE challenge
    const verifier = generateCodeVerifier();
    const challenge = await generateCodeChallenge(verifier);
    
    sessionStorage.setItem('pkce_verifier', verifier);
    sessionStorage.setItem('oauth_state', generateState());
    
    // Redirect to OAuth provider
    const params = new URLSearchParams({
      client_id: process.env.REACT_APP_CLIENT_ID,
      redirect_uri: window.location.origin + '/callback',
      response_type: 'code',
      scope: 'openid profile email',
      code_challenge: challenge,
      code_challenge_method: 'S256',
      state: sessionStorage.getItem('oauth_state')
    });
    
    window.location.href = `https://oauth.provider.com/authorize?${params}`;
  }
  
  async function handleCallback() {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const state = params.get('state');
    
    // Verify state
    if (state !== sessionStorage.getItem('oauth_state')) {
      throw new Error('Invalid state');
    }
    
    // Exchange code for tokens
    const verifier = sessionStorage.getItem('pkce_verifier');
    const response = await fetch('/api/auth/token', {
      method: 'POST',
      body: JSON.stringify({ code, verifier }),
      headers: { 'Content-Type': 'application/json' }
    });
    
    const { user } = await response.json();
    setUser(user);
  }
  
  return { user, login, handleCallback };
}
Click to expand code...

Common Providers

Google

javascript
Authorization URL: https://accounts.google.com/o/oauth2/v2/auth
Token URL: https://oauth2.googleapis.com/token
UserInfo URL: https://www.googleapis.com/oauth2/v2/userinfo
JWKS URL: https://www.googleapis.com/oauth2/v3/certs
Click to expand code...

GitHub

javascript
Authorization URL: https://github.com/login/oauth/authorize
Token URL: https://github.com/login/oauth/access_token
User URL: https://api.github.com/user
Click to expand code...

Auth0 (Enterprise)

javascript
Authorization URL: https://{tenant}.auth0.com/authorize
Token URL: https://{tenant}.auth0.com/oauth/token
UserInfo URL: https://{tenant}.auth0.com/userinfo
Click to expand code...

Interview Tips 💡

When discussing OAuth in system design interviews:

  1. Explain the problem: "Users shouldn't give third-party apps their passwords..."
  2. Choose right flow: "For web app with backend, use Authorization Code Flow. For SPA, use PKCE..."
  3. Security concerns: "Store tokens in HttpOnly cookies, not localStorage. Always validate state parameter..."
  4. Token management: "Access tokens expire in 1 hour, use refresh tokens to get new ones..."
  5. Scope limitation: "Request minimum scopes needed, follow principle of least privilege..."
  6. Real examples: "Google 'Sign in with Google' uses OAuth 2.0 + OIDC..."

Related Concepts

About ScaleWiki

ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.

Read more about our Editorial Guidelines & Authorship.

Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.

Related Articles