OAuth2 Implementation
OAuth2 Fundamentals
OAuth2 is the industry-standard protocol for authorization. It enables third-party applications to obtain limited access to user resources without exposing credentials.
Grant Types
Authorization Code Grant (with PKCE)
The recommended flow for public clients:
// PKCE code challenge generation
const crypto = require("crypto");
function generatePKCE() {
const verifier = crypto.randomBytes(32)
.toString("base64url");
const challenge = crypto.createHash("sha256")
.update(verifier)
.digest("base64url");
return { verifier, challenge };
}
// Authorization request
const { verifier, challenge } = generatePKCE();
const authUrl = `https://auth.example.com/authorize?
response_type=code&
client_id=app123&
redirect_uri=https://app.example.com/callback&
code_challenge=${challenge}&
code_challenge_method=S256&
scope=openid%20profile`;
// Token exchange
async function exchangeCode(code, verifier) {
const resp = await fetch("https://auth.example.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: code,
client_id: "app123",
code_verifier: verifier,
redirect_uri: "https://app.example.com/callback"
})
});
return resp.json();
}
Client Credentials Grant
For server-to-server communication:
import requests
def get_client_credentials_token(client_id, client_secret, scope):
resp = requests.post(
"https://auth.example.com/token",
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": scope
},
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
return resp.json()["access_token"]
Token Handling
Access Token Validation
// JWT verification middleware
const jwt = require("jsonwebtoken");
const jwksClient = require("jwks-rsa");
const client = jwksClient({
jwksUri: "https://auth.example.com/.well-known/jwks.json"
});
function verifyToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing token" });
}
const token = authHeader.split(" ")[1];
jwt.verify(token, (header, callback) => {
client.getSigningKey(header.kid, (err, key) => {
callback(err, key.getPublicKey());
});
}, {
algorithms: ["RS256"],
issuer: "https://auth.example.com",
audience: "https://api.example.com"
}, (err, decoded) => {
if (err) return res.status(401).json({ error: "Invalid token" });
req.user = decoded;
next();
});
}
Refresh Token Rotation
def rotate_refresh_token(old_token):
# Verify the old refresh token
token_data = verify_refresh_token(old_token)
# Revoke the old token
revoke_token(old_token)
# Issue new tokens
new_access = create_access_token(token_data["user_id"])
new_refresh = create_refresh_token(token_data["user_id"])
return {
"access_token": new_access,
"refresh_token": new_refresh,
"expires_in": 3600
}
Best Practices
oauth2_best_practices:
- Always use PKCE for public clients
- Use short-lived access tokens (15-60 minutes)
- Implement refresh token rotation
- Use RS256 or ES256 for JWT signing
- Validate all claims (iss, aud, exp, nbf)
- Store tokens securely (httpOnly cookies, secure storage)
- Implement token revocation
- Log all token issuance and usage
Conclusion
OAuth2 is complex but essential for modern authentication. PKCE makes authorization code flow safe for single-page apps. Use short-lived access tokens with refresh token rotation. Always validate tokens thoroughly on the server side. Keep your JWKS endpoint secure and rotate signing keys regularly.