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
- OAuth 2.0 Core Concepts
- OAuth 2.0 Grant Types
- Authorization Code Flow
- Authorization Code with PKCE
- Client Credentials Flow
- Device Code Flow
- Refresh Tokens
- Deprecated Grant Types
- OAuth 2.0 vs OpenID Connect
- Security Best Practices
- Common OAuth Mistakes
- Real World Examples
- Implementing OAuth: A Checklist
- OAuth Libraries and Tools
- Key Takeaways
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:
- Give the calendar app your Google username and password
- Hope they store it securely (spoiler: many did not)
- Trust them not to read your emails, access your Drive, or do anything else
- 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 |
| Deprecated | ||
| 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:
- Your app redirects to Google with scope
openid email profile - User authenticates on Google’s login page
- Google returns an authorization code
- Your backend exchanges the code for tokens (access token + ID token)
- 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
-
OAuth 2.0 is authorization, not authentication. It grants access to resources. Use OpenID Connect if you need to verify user identity.
-
Authorization Code + PKCE is the recommended flow. Use it for web apps, mobile apps, and SPAs. Add a client secret for server side apps.
-
Never use implicit flow. It is deprecated. Use Authorization Code + PKCE instead.
-
Keep access tokens short lived. 15 minutes to 1 hour. Use refresh tokens for longer sessions.
-
Always validate the state parameter. It prevents CSRF attacks.
-
Scopes are permissions. Request only what you need. Users trust apps that ask for less.
-
Use established libraries. Do not roll your own OAuth implementation.
Further Reading:
- Why JWT Replaced Sessions - Deep dive into JSON Web Tokens for authentication
- Passkeys Explained - The future of passwordless authentication
- Caching Strategies Explained - For caching OAuth tokens effectively
- OAuth 2.0 Simplified - Aaron Parecki’s comprehensive guide
- RFC 6749: OAuth 2.0 Authorization Framework - The official specification
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.