You have probably used OAuth 2.0 today without knowing it. Every time you click “Sign in with Google” or connect an app to your GitHub account, OAuth is working behind the scenes.

But what actually happens when you click that button? Where does your data go? And why do apps ask for specific permissions?

OAuth 2.0 solves a real problem: how do you let one app access your data on another app without giving away your password?

Before OAuth, the answer was terrifying. You gave third party apps your actual password. That app could then do anything with your account. There was no way to limit access or revoke it without changing your password everywhere.

OAuth changed that. It gives apps limited access through tokens, not passwords. You stay in control of what they can do.


Table of Contents


The Problem OAuth Solves

Imagine you want to use a calendar app that can read your Google Calendar events. Before OAuth, you would have to:

  1. Give the calendar app your Google username and password
  2. Hope they store it securely (spoiler: many did not)
  3. Trust them not to read your emails, access your Drive, or do anything else
  4. Change your Google password to revoke access (which logs you out everywhere)

This is obviously bad. Your password is the key to everything. Handing it to third parties is a security nightmare.

OAuth solves this with a simple idea: give apps a limited key instead of the master key.

flowchart LR
    subgraph Before["Before OAuth"]
        direction TB
        U1[You] -->|"Give password"| App1[Calendar App]
        App1 -->|"Uses your password"| G1[Google]
    end
    
    subgraph After["With OAuth"]
        direction TB
        U2[You] -->|"Approve access"| G2[Google]
        G2 -->|"Issue limited token"| App2[Calendar App]
        App2 -->|"Uses token"| G2
    end
    
    style U1 fill:#fef3c7,stroke:#d97706,stroke-width:2px
    style U2 fill:#d1fae5,stroke:#059669,stroke-width:2px
    style App1 fill:#fee2e2,stroke:#dc2626,stroke-width:2px
    style App2 fill:#d1fae5,stroke:#059669,stroke-width:2px

With OAuth, you approve access on Google’s website. Google gives the app a token that can only do specific things, like read calendar events. The app never sees your password. You can revoke access anytime without changing your password.

OAuth 2.0 Core Concepts

Before diving into flows, let us define the key players:

The Four Roles

Role What It Is Example
Resource Owner The user who owns the data You
Client The app requesting access A calendar app
Authorization Server Issues tokens after user approval Google’s OAuth server
Resource Server Hosts the protected data Google Calendar API

Tokens

OAuth uses two types of tokens:

Access Token: A credential that grants access to protected resources. Like a hotel key card that opens specific doors. Usually short lived (minutes to hours).

Refresh Token: A credential used to get new access tokens without re-authentication. Like a voucher that lets you get a new key card. Lives longer than access tokens.

Scopes

Scopes define what an access token can do. They are permissions that limit the token’s power.

1
scope=read:calendar write:calendar read:profile

When you see a consent screen listing “This app wants to view your calendar”, those are scopes. The app only gets access to what you approve.

Client Credentials

When you register your app with an OAuth provider, you receive two values:

Client ID: A public identifier for your application. Safe to include in frontend code.

Client Secret: A private key known only to your application and the authorization server. Never expose this in client side code or version control.

OAuth 2.0 Grant Types

OAuth 2.0 defines several ways to get tokens. Each grant type is designed for different use cases.

Grant Type Use Case Security Level
Authorization Code Web apps with backend High
Authorization Code + PKCE Mobile apps, SPAs High
Client Credentials Machine to machine High
Device Code Smart TVs, CLI tools Medium
Refresh Token Renewing access High
Implicit Browser apps Deprecated
Password Trusted apps Deprecated

Let us look at each one.


Authorization Code Flow

The Authorization Code flow is the most common and secure OAuth flow. It is designed for applications that can securely store secrets, like server side web applications.

How It Works

sequenceDiagram
    participant User
    participant App as Your App
    participant Auth as Authorization Server
    participant API as Resource Server
    
    User->>App: 1. Click "Login with Google"
    App->>Auth: 2. Redirect to authorization endpoint
    Note over Auth: Show login and consent screen
    User->>Auth: 3. Enter credentials, approve access
    Auth->>App: 4. Redirect with authorization code
    App->>Auth: 5. Exchange code for tokens
    Note over App,Auth: Include client secret
    Auth->>App: 6. Return access token + refresh token
    App->>API: 7. API request with access token
    API->>App: 8. Return protected resource

Step by Step

Step 1: Authorization Request

Your app redirects the user to the authorization server:

1
2
3
4
5
6
https://accounts.google.com/oauth/authorize?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=read:calendar
  &state=xyz123
Parameter Purpose
response_type=code Request an authorization code
client_id Your app’s identifier
redirect_uri Where to send the user after authorization
scope What permissions you need
state Random value to prevent CSRF attacks

Step 2: User Approves

The authorization server shows a login page (if needed) and consent screen. The user sees what permissions you are requesting.

Step 3: Authorization Code

After approval, the authorization server redirects back to your app:

1
2
3
https://yourapp.com/callback?
  code=AUTH_CODE_HERE
  &state=xyz123

The authorization code is a short lived, one time use code. It cannot access resources directly.

Step 4: Exchange Code for Tokens

Your backend exchanges the code for tokens:

1
2
3
4
5
6
curl -X POST https://accounts.google.com/oauth/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE_HERE" \
  -d "redirect_uri=https://yourapp.com/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Step 5: Receive Tokens

1
2
3
4
5
6
7
{
  "access_token": "ya29.a0AfH6SMBx...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "1//0eXx...",
  "scope": "read:calendar"
}

Now your app can make API calls using the access token.

Code Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const express = require('express');
const axios = require('axios');

const app = express();

const CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
const REDIRECT_URI = 'https://yourapp.com/callback';

// Step 1: Redirect to Google
app.get('/login', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');
  req.session.oauthState = state;
  
  const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
  authUrl.searchParams.set('client_id', CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', 'openid email profile');
  authUrl.searchParams.set('state', state);
  
  res.redirect(authUrl.toString());
});

// Step 4: Handle callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Verify state to prevent CSRF
  if (state !== req.session.oauthState) {
    return res.status(403).send('Invalid state');
  }
  
  // Exchange code for tokens
  const tokenResponse = await axios.post(
    'https://oauth2.googleapis.com/token',
    new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
    })
  );
  
  const { access_token, refresh_token } = tokenResponse.data;
  
  // Store tokens securely (database, encrypted session, etc.)
  req.session.accessToken = access_token;
  
  res.redirect('/dashboard');
});

When to Use Authorization Code

Server side web applications

Applications that can securely store client secrets

Traditional multi page applications


Authorization Code with PKCE

PKCE (Proof Key for Code Exchange, pronounced “pixy”) adds security for clients that cannot safely store secrets. This includes mobile apps, single page applications (SPAs), and desktop apps.

The problem: mobile apps and SPAs run on devices users control. You cannot embed a secret in a mobile app because someone can extract it. Without a secret, what stops an attacker from using a stolen authorization code?

PKCE solves this with a dynamic secret created for each authorization request.

How PKCE Works

sequenceDiagram
    participant User
    participant App as Mobile App
    participant Auth as Authorization Server
    
    Note over App: Generate code_verifier (random)
    Note over App: Create code_challenge = SHA256(verifier)
    
    User->>App: 1. Tap "Login"
    App->>Auth: 2. Redirect with code_challenge
    User->>Auth: 3. Approve access
    Auth->>App: 4. Redirect with authorization code
    App->>Auth: 5. Exchange code + code_verifier
    Note over Auth: Verify SHA256(verifier) = challenge
    Auth->>App: 6. Return tokens

The PKCE Parameters

Code Verifier: A random string (43-128 characters) generated by the client.

Code Challenge: A SHA256 hash of the code verifier, base64url encoded.

1
2
3
4
5
6
7
8
9
// Generate code verifier
const codeVerifier = crypto.randomBytes(32)
  .toString('base64url');

// Generate code challenge
const codeChallenge = crypto
  .createHash('sha256')
  .update(codeVerifier)
  .digest('base64url');

Authorization Request with PKCE

1
2
3
4
5
6
7
8
https://auth.example.com/authorize?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=myapp://callback
  &scope=read:data
  &state=xyz123
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

Token Exchange with PKCE

1
2
3
4
5
6
curl -X POST https://auth.example.com/token \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE_HERE" \
  -d "redirect_uri=myapp://callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

Notice there is no client_secret. The code_verifier proves the same client that started the flow is finishing it.

Why PKCE Matters

Without PKCE, an attacker who intercepts the authorization code (through a malicious app registered on the same custom URL scheme) could exchange it for tokens.

With PKCE, the attacker would also need the code_verifier, which never left the original app.

When to Use PKCE

Mobile applications (iOS, Android)

Single page applications (React, Vue, Angular)

Desktop applications (Electron)

Any public client that cannot store secrets

Best Practice: Use PKCE for all authorization code flows, even if you have a client secret. It adds security with no downside.


Client Credentials Flow

Client Credentials is for machine to machine communication. No user is involved. The application authenticates using its own credentials.

sequenceDiagram
    participant App as Backend Service
    participant Auth as Authorization Server
    participant API as Resource Server
    
    App->>Auth: 1. Request token with client credentials
    Note over App,Auth: client_id + client_secret
    Auth->>App: 2. Return access token
    App->>API: 3. API request with access token
    API->>App: 4. Return data

Token Request

1
2
3
4
curl -X POST https://auth.example.com/token \
  -u "CLIENT_ID:CLIENT_SECRET" \
  -d "grant_type=client_credentials" \
  -d "scope=read:analytics"

Response

1
2
3
4
5
6
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:analytics"
}

When to Use Client Credentials

Microservices calling other microservices

Backend cron jobs accessing APIs

System to system integrations

Never use when a user is involved


Device Code Flow

Device Code flow is for devices with limited input capabilities like smart TVs, game consoles, or CLI tools. Users authorize on a secondary device like their phone.

sequenceDiagram
    participant TV as Smart TV
    participant Auth as Authorization Server
    participant Phone as User's Phone
    
    TV->>Auth: 1. Request device code
    Auth->>TV: 2. Return device_code + user_code + URL
    Note over TV: Display: Go to example.com/device<br/>Enter code: WDJB-MJHT
    
    Phone->>Auth: 3. User visits URL, enters code
    Phone->>Auth: 4. User approves
    
    loop Poll until approved
        TV->>Auth: 5. Check if authorized
        Auth->>TV: 6. Pending / Approved
    end
    
    Auth->>TV: 7. Return access token

Step 1: Request Device Code

1
2
3
curl -X POST https://auth.example.com/device/code \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "scope=read:library"

Response

1
2
3
4
5
6
7
{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://example.com/device",
  "expires_in": 1800,
  "interval": 5
}

The device displays the URL and user code. The user visits the URL on their phone, enters the code, and approves.

Step 2: Poll for Token

The device polls the token endpoint every interval seconds:

1
2
3
4
curl -X POST https://auth.example.com/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  -d "device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS" \
  -d "client_id=YOUR_CLIENT_ID"

Responses while pending:

1
2
3
{
  "error": "authorization_pending"
}

After user approves:

1
2
3
4
5
6
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8xLOxBtZp8"
}

When to Use Device Code

Smart TVs and streaming devices

Game consoles

CLI tools and developer utilities

IoT devices with limited UI


Refresh Tokens

Access tokens expire. Without refresh tokens, users would have to re-authenticate every time. Refresh tokens solve this by letting your app get new access tokens silently.

sequenceDiagram
    participant App
    participant API as Resource Server
    participant Auth as Authorization Server
    
    App->>API: 1. API request with access token
    API->>App: 2. 401 Token expired
    
    App->>Auth: 3. Request new access token
    Note over App,Auth: Use refresh token
    Auth->>App: 4. New access token (+ optional new refresh token)
    
    App->>API: 5. Retry with new access token
    API->>App: 6. Success

Token Refresh Request

1
2
3
4
5
curl -X POST https://auth.example.com/token \
  -d "grant_type=refresh_token" \
  -d "refresh_token=8xLOxBtZp8" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Response

1
2
3
4
5
6
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tGzv3JOkF0"
}

Some servers return a new refresh token with each refresh (refresh token rotation). This limits the damage if a refresh token is stolen.

Refresh Token Best Practices

Practice Why
Store refresh tokens securely They can get new access tokens indefinitely
Use refresh token rotation Detects if a token is stolen and used twice
Set reasonable expiration 7-30 days for most apps
Revoke on logout Delete refresh tokens when users log out

Deprecated Grant Types

Two grant types from OAuth 2.0 are now deprecated. Do not use them for new applications.

Implicit Flow (Deprecated)

The implicit flow was designed for browser apps that could not make backend requests. It returned access tokens directly in the URL fragment.

Why it is deprecated:

  • Access tokens exposed in browser history
  • Tokens visible in referrer headers
  • No refresh tokens possible
  • Vulnerable to token leakage

Use Instead: Authorization Code with PKCE

Resource Owner Password Credentials (Deprecated)

The password grant lets users enter their username and password directly in the client app.

Why it is deprecated:

  • Users enter passwords in third party apps (the problem OAuth was created to solve)
  • Cannot support MFA properly
  • Apps store or transmit passwords

Use Instead: Authorization Code flow with the authorization server handling credentials


OAuth 2.0 vs OpenID Connect

A common confusion: OAuth 2.0 is for authorization (what you can access), not authentication (who you are).

OAuth tells your app “this token can access calendar events”. It does not tell you who the user is.

OpenID Connect (OIDC) is a layer on top of OAuth that adds authentication.

graph TB
    subgraph OIDC["OpenID Connect"]
        ID["ID Token<br/>(who you are)"]
        UserInfo["UserInfo Endpoint"]
    end
    
    subgraph OAuth["OAuth 2.0"]
        Access["Access Token<br/>(what you can access)"]
        Refresh["Refresh Token"]
    end
    
    OIDC --> OAuth
    
    style OIDC fill:#e0f2fe,stroke:#0284c7,stroke-width:2px
    style OAuth fill:#dcfce7,stroke:#16a34a,stroke-width:2px

What OIDC Adds

Feature OAuth 2.0 OpenID Connect
Authorization Yes Yes
Authentication No Yes
ID Token No Yes
Standard user claims No Yes (name, email, etc.)
Discovery document No Yes (/.well-known/openid-configuration)

ID Token

OIDC introduces the ID token, a JWT containing user identity claims:

1
2
3
4
5
6
7
8
9
10
{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",
  "aud": "YOUR_CLIENT_ID",
  "exp": 1706540400,
  "iat": 1706536800,
  "email": "user@example.com",
  "email_verified": true,
  "name": "John Smith"
}

When to Use Which

Scenario Use
Need to access an API on user’s behalf OAuth 2.0
Need to know who the user is OpenID Connect
Social login (Sign in with Google) OpenID Connect
Machine to machine API access OAuth 2.0

Security Best Practices

Always Use HTTPS

Never use OAuth over HTTP. Tokens in transit would be visible to anyone on the network.

Validate Redirect URIs

Register exact redirect URIs with your authorization server. Never use wildcards.

1
2
Good: https://myapp.com/oauth/callback
Bad:  https://myapp.com/*

Use the State Parameter

The state parameter prevents CSRF attacks. Generate a random value, store it in the session, and verify it on callback.

1
2
3
4
5
6
7
8
// Generate state
const state = crypto.randomBytes(16).toString('hex');
req.session.oauthState = state;

// Verify on callback
if (req.query.state !== req.session.oauthState) {
  throw new Error('Invalid state');
}

Keep Access Tokens Short Lived

Short expiration limits damage if a token is compromised:

Token Type Recommended Lifetime
Access Token 15 minutes to 1 hour
Refresh Token 7 to 30 days
Authorization Code 10 minutes max

Use PKCE for All Authorization Code Flows

Even if you have a client secret, PKCE adds defense in depth.

Store Tokens Securely

Token Storage
Access Token (server) Encrypted session or secure cookie
Access Token (browser) Memory only, not localStorage
Refresh Token Server side storage with encryption

Implement Token Revocation

Let users revoke access. When they disconnect an app or change their password, invalidate tokens.


Common OAuth Mistakes

Mistake 1: Storing Tokens in localStorage

1
2
3
4
5
// Bad: Vulnerable to XSS
localStorage.setItem('access_token', token);

// Better: Keep in memory, use httpOnly cookies for refresh
let accessToken = token;

Mistake 2: Ignoring the State Parameter

Without state validation, attackers can craft malicious authorization URLs and trick users into authorizing access to attacker-controlled accounts.

Mistake 3: Using Long-Lived Access Tokens

Access tokens should expire quickly. Use refresh tokens for long-lived sessions.

Mistake 4: Requesting Too Many Scopes

Only request scopes you actually need. Users are more likely to deny access when they see a long list of permissions.

1
2
Bad:  scope=read:email read:calendar write:calendar read:drive write:drive
Good: scope=read:calendar

Mistake 5: Using Implicit Flow for New Apps

The implicit flow is deprecated. Use Authorization Code with PKCE instead, even for SPAs.


Real World Examples

Google Sign-In

Google uses OpenID Connect (OAuth 2.0 + identity layer) for Sign in with Google. When you click that button:

  1. Your app redirects to Google with scope openid email profile
  2. User authenticates on Google’s login page
  3. Google returns an authorization code
  4. Your backend exchanges the code for tokens (access token + ID token)
  5. The ID token contains user info (name, email, profile picture)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Google OAuth scopes for sign-in
const scopes = [
  'openid',           // Required for OIDC
  'email',            // User's email address
  'profile'           // Name and profile picture
];

// The ID token payload
{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",  // Unique user ID
  "email": "user@gmail.com",
  "email_verified": true,
  "name": "John Smith",
  "picture": "https://lh3.googleusercontent.com/a/...",
  "iat": 1706536800,
  "exp": 1706540400
}

GitHub OAuth

GitHub uses standard OAuth 2.0 for authorizing apps to access repositories, gists, and user data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Step 1: Redirect user to GitHub
https://github.com/login/oauth/authorize?
  client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=repo user:email
  &state=random123

# Step 2: Exchange code for token
curl -X POST https://github.com/login/oauth/access_token \
  -H "Accept: application/json" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=AUTH_CODE_HERE"

# Response
{
  "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
  "scope": "repo,user:email",
  "token_type": "bearer"
}

# Step 3: Use the token
curl -H "Authorization: Bearer gho_16C7e42F292c6912E7710c838347Ae178B4a" \
  https://api.github.com/user

GitHub’s OAuth scopes are granular. You can request just public_repo instead of full repo access if you only need public repositories.

Spotify API

Spotify uses OAuth 2.0 with Authorization Code flow for user data and Client Credentials for non-user endpoints.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// For user playlists (requires user authorization)
const userScopes = [
  'playlist-read-private',
  'playlist-modify-public',
  'user-read-currently-playing'
];

// For searching tracks (no user needed)
// Use Client Credentials flow
const tokenResponse = await fetch('https://accounts.spotify.com/api/token', {
  method: 'POST',
  headers: {
    'Authorization': 'Basic ' + btoa(CLIENT_ID + ':' + CLIENT_SECRET),
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: 'grant_type=client_credentials'
});

Slack App Authorization

Slack uses OAuth 2.0 with specific scopes for bot permissions and user tokens.

1
2
3
4
5
6
7
8
# Bot token scopes (what your bot can do)
chat:write        - Send messages
channels:read     - List public channels
users:read        - Access user information

# User token scopes (on behalf of a user)
files:write       - Upload files as the user
reactions:write   - Add reactions as the user

The authorization URL includes the specific permissions:

1
2
3
4
5
https://slack.com/oauth/v2/authorize?
  client_id=YOUR_CLIENT_ID
  &scope=chat:write,channels:read
  &user_scope=files:write
  &redirect_uri=https://yourapp.com/slack/callback

Implementing OAuth: A Checklist

Before launching your OAuth integration:

Security Checklist

  • Using HTTPS for all OAuth endpoints
  • Registered exact redirect URIs (no wildcards)
  • Implementing state parameter for CSRF protection
  • Using PKCE for public clients (mobile apps, SPAs)
  • Storing tokens securely (not in localStorage)
  • Access tokens expire in under 1 hour
  • Refresh tokens stored server side with encryption
  • Token revocation implemented on logout

User Experience Checklist

  • Requesting only necessary scopes
  • Clear error messages for authorization failures
  • Handling token refresh transparently
  • Showing connected accounts in user settings
  • Providing a way to disconnect accounts

Implementation Checklist

  • Using a well-maintained OAuth library (not rolling your own)
  • Validating all tokens before use
  • Logging authorization events for debugging
  • Handling rate limits from OAuth providers
  • Graceful fallback when tokens are revoked

OAuth Libraries and Tools

Do not implement OAuth from scratch. Use battle tested libraries:

JavaScript/Node.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// passport.js - Most popular Node.js auth
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: '/auth/google/callback'
  },
  (accessToken, refreshToken, profile, done) => {
    // User authenticated, save to database
    return done(null, profile);
  }
));

Python

1
2
3
4
5
6
7
8
9
10
11
# authlib - Full featured OAuth library
from authlib.integrations.flask_client import OAuth

oauth = OAuth(app)
oauth.register(
    name='google',
    client_id=os.environ['GOOGLE_CLIENT_ID'],
    client_secret=os.environ['GOOGLE_CLIENT_SECRET'],
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile'}
)

Testing Tools

  • OAuth 2.0 Playground (Google): Test OAuth flows interactively
  • Postman: Built in OAuth 2.0 authorization
  • jwt.io: Decode and inspect tokens

Key Takeaways

  1. OAuth 2.0 is authorization, not authentication. It grants access to resources. Use OpenID Connect if you need to verify user identity.

  2. Authorization Code + PKCE is the recommended flow. Use it for web apps, mobile apps, and SPAs. Add a client secret for server side apps.

  3. Never use implicit flow. It is deprecated. Use Authorization Code + PKCE instead.

  4. Keep access tokens short lived. 15 minutes to 1 hour. Use refresh tokens for longer sessions.

  5. Always validate the state parameter. It prevents CSRF attacks.

  6. Scopes are permissions. Request only what you need. Users trust apps that ask for less.

  7. Use established libraries. Do not roll your own OAuth implementation.


Further Reading:

Building secure applications? See How Stripe Prevents Double Payments for idempotency patterns. Working with distributed systems? Check out How Kafka Works for handling high-throughput event streams with proper authentication.