secure_oauth_state_manager
v1.0.2
Published
Secure OAuth state and PKCE manager with expiration, replay protection, and multi-storage support.
Maintainers
Readme
secure_oauth_state_manager
An enterprise-grade, lightweight npm package to manage secure OAuth states and PKCE credentials with expiration, replay protection, and multi-storage support (Memory, Redis, and Cloudflare KV). Fully compatible with Node.js and Edge runtimes (like Cloudflare Workers and Vercel Edge).
The Problem It Solves
When implementing OAuth flows, security vulnerabilities like CSRF (Cross-Site Request Forgery) and authorization code reuse (replay attacks) are common.
Most implementations use weak randomness (Math.random()), lack state expiration, omit replay protection, or fail to safely link states to the user session. This package enforces security best practices out of the box:
- Cryptographic Signatures (JWT): Ensures states cannot be forged or tampered with.
- Expiration Support: Sets a strict window of time in which the state is valid.
- Replay Protection: Guarantees a state is used exactly once, preventing replay attacks.
- PKCE Generation: Generates high-entropy cryptographic code verifiers and SHA-256 challenges automatically.
Features
- ⚡ Edge Compatible: Zero Node-native dependencies. Uses the standard Web Crypto API.
- 🕒 Expiration Check: Reject states that exceed a configured lifetime (e.g.,
10m). - 🛡️ Replay Protection: Single-use state verification. Expired identifiers are purged automatically.
- 📦 Multiple Storage Backends: Built-in wrappers for Memory caching, Redis, and Cloudflare KV.
- 🏷️ Provider Awareness: Handle multi-provider OAuth (Spotify, Google, GitHub, etc.) with a single setup.
Installation
npm install secure_oauth_state_managerQuick Start (Global Helper Functions)
Configure your secret key globally and generate secure states:
import { configure, createState, verifyState } from "secure_oauth_state_manager";
// 1. Configure the global defaults
configure({
secret: "your-32-character-secret-key-goes-here",
defaultExpiresIn: "10m" // default lifetime for states
});
// 2. Generate a state token (ideal for redirecting to OAuth provider)
const state = await createState({
provider: "spotify",
userId: "user-123",
customClaim: "premium" // add any arbitrary claims
});
// Output is a signed JWT state token
// e.g. "eyJhbGciOiJIUzI1NiJ9..."
// 3. Verify the state in your redirect callback
const result = await verifyState(state);
if (result.valid) {
console.log("Success! Authenticating for user:", result.userId);
console.log("Provider was:", result.provider);
} else {
console.error("Verification failed. Reason:", result.reason);
// Rejection reasons: "expired", "already_used", "invalid_token", etc.
}Advanced Usage (Object-Oriented Manager)
If you are dealing with multiple environments, different keys, or custom storages, you can instantiate the manager directly:
import { OAuthStateManager, RedisStorage } from "secure_oauth_state_manager";
import { createClient } from "redis";
// Connect your Redis client
const redisClient = createClient();
await redisClient.connect();
const oauth = new OAuthStateManager({
secret: process.env.JWT_SECRET,
storage: new RedisStorage(redisClient), // Enable Redis for persistent, cluster-safe replay checks
defaultExpiresIn: "5m"
});
// Generate state token AND PKCE parameters together
const { state, codeVerifier, codeChallenge } = await oauth.generate({
provider: "spotify",
userId: "123",
pkce: true // Auto-generate PKCE parameters (default: true)
});Real-Life Express Integration with PKCE
Here is how to structure a secure OAuth flow in an Express backend:
import express from "express";
import cookieParser from "cookie-parser";
import { OAuthStateManager } from "secure_oauth_state_manager";
const app = express();
app.use(cookieParser());
const oauth = new OAuthStateManager({
secret: "your-jwt-signing-secret-key-at-least-32-chars",
defaultExpiresIn: "10m"
});
// Initiate Login Route
app.get("/login", async (req, res) => {
const { state, codeVerifier, codeChallenge } = await oauth.generate({
provider: "spotify",
userId: req.user?.id || "anonymous"
});
// Store the codeVerifier in a secure, HTTP-only cookie
res.cookie("oauth_code_verifier", codeVerifier, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 10 * 60 * 1000 // 10 minutes
});
// Redirect client to Spotify Authorization Consent Screen
const spotifyUrl = `https://accounts.spotify.com/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_CALLBACK_URL&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
res.redirect(spotifyUrl);
});
// OAuth Callback Route
app.get("/callback", async (req, res) => {
const { code, state } = req.query;
// 1. Verify and invalidate the state (replay check)
const verification = await oauth.verify(state);
if (!verification.valid) {
return res.status(400).send(`State validation failed: ${verification.reason}`);
}
// 2. Retrieve code verifier from secure cookie
const codeVerifier = req.cookies.oauth_code_verifier;
res.clearCookie("oauth_code_verifier");
if (!codeVerifier) {
return res.status(400).send("Session expired or missing code verifier.");
}
// 3. Request Spotify Token safely by sending code and code_verifier
// POST to https://accounts.spotify.com/api/token ...
res.send(`Logged in as user ${verification.userId} through ${verification.provider}!`);
});Storage Provider Wrappers
In-Memory Storage (Default)
Safe for development and single-node applications. Includes an automatic self-cleaning timer to sweep expired identifiers from memory.
import { MemoryStorage } from "secure_oauth_state_manager";
const storage = new MemoryStorage();Redis Storage
Recommended for production and distributed environments. Supports standard node-redis and ioredis clients. Uses Redis TTL (SETEX) to auto-expire state tokens.
import { RedisStorage } from "secure_oauth_state_manager";
const storage = new RedisStorage(redisClient);Cloudflare KV Storage
Ideal for serverless architectures. Integrates natively with Cloudflare Workers Key-Value namespaces.
import { CloudflareKVStorage } from "secure_oauth_state_manager";
const storage = new CloudflareKVStorage(env.MY_KV_NAMESPACE);License
MIT
