oauth-patterns
OIDC flows, PKCE implementation, token refresh strategies, social login integration, and secure session management.
Best use case
oauth-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
OIDC flows, PKCE implementation, token refresh strategies, social login integration, and secure session management.
Teams using oauth-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/oauth-patterns/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How oauth-patterns Compares
| Feature / Agent | oauth-patterns | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
OIDC flows, PKCE implementation, token refresh strategies, social login integration, and secure session management.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# OAuth Patterns
Secure authentication and authorization patterns with OAuth 2.0 and OpenID Connect.
## Authorization Code Flow with PKCE
```typescript
// PKCE (Proof Key for Code Exchange): required for public clients (SPA, mobile)
import crypto from 'crypto'
// Step 1: Generate PKCE verifier and challenge
function generatePKCE(): { verifier: string; challenge: string } {
const verifier = crypto.randomBytes(32).toString('base64url')
const challenge = crypto
.createHash('sha256')
.update(verifier)
.digest('base64url')
return { verifier, challenge }
}
// Step 2: Build authorization URL
function getAuthorizationUrl(config: OAuthConfig): { url: string; state: string; pkce: PKCE } {
const state = crypto.randomBytes(16).toString('hex')
const pkce = generatePKCE()
const params = new URLSearchParams({
response_type: 'code',
client_id: config.clientId,
redirect_uri: config.redirectUri,
scope: 'openid profile email',
state,
code_challenge: pkce.challenge,
code_challenge_method: 'S256',
prompt: 'consent', // Force consent screen
nonce: crypto.randomUUID(), // Replay protection
})
return {
url: `${config.authorizationEndpoint}?${params}`,
state,
pkce,
}
}
// Step 3: Exchange code for tokens
async function exchangeCodeForTokens(
code: string,
verifier: string,
config: OAuthConfig
): Promise<TokenSet> {
const response = await fetch(config.tokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: config.redirectUri,
client_id: config.clientId,
code_verifier: verifier,
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(`Token exchange failed: ${error.error_description}`)
}
return response.json() as Promise<TokenSet>
}
```
## Token Refresh Strategy
```typescript
interface TokenSet {
access_token: string
refresh_token: string
id_token: string
expires_in: number // seconds
token_type: 'Bearer'
}
class TokenManager {
private refreshTimer: NodeJS.Timeout | null = null
async setTokens(tokens: TokenSet): Promise<void> {
// Store tokens securely (httpOnly cookies or encrypted storage)
await secureStore.set('access_token', tokens.access_token)
await secureStore.set('refresh_token', tokens.refresh_token)
// Schedule refresh before expiry (refresh at 75% of lifetime)
const refreshIn = tokens.expires_in * 0.75 * 1000
this.scheduleRefresh(refreshIn)
}
private scheduleRefresh(delayMs: number): void {
if (this.refreshTimer) clearTimeout(this.refreshTimer)
this.refreshTimer = setTimeout(() => this.refresh(), delayMs)
}
private async refresh(): Promise<void> {
const refreshToken = await secureStore.get('refresh_token')
if (!refreshToken) {
this.emit('session_expired')
return
}
try {
const response = await fetch(config.tokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: config.clientId,
}),
})
if (!response.ok) throw new Error('Refresh failed')
const tokens = await response.json() as TokenSet
await this.setTokens(tokens)
} catch (err) {
// Refresh token expired or revoked
await this.clearTokens()
this.emit('session_expired')
}
}
async clearTokens(): Promise<void> {
if (this.refreshTimer) clearTimeout(this.refreshTimer)
await secureStore.delete('access_token')
await secureStore.delete('refresh_token')
}
}
```
## Social Login Integration
```typescript
// Provider-specific configurations
const OAUTH_PROVIDERS: Record<string, OAuthConfig> = {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenEndpoint: 'https://oauth2.googleapis.com/token',
userInfoEndpoint: 'https://www.googleapis.com/oauth2/v3/userinfo',
scopes: ['openid', 'profile', 'email'],
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
authorizationEndpoint: 'https://github.com/login/oauth/authorize',
tokenEndpoint: 'https://github.com/login/oauth/access_token',
userInfoEndpoint: 'https://api.github.com/user',
scopes: ['user:email'],
},
}
// Callback handler: link social account to internal user
async function handleOAuthCallback(
provider: string,
code: string,
state: string
): Promise<{ user: User; session: Session }> {
// Verify state matches stored state (CSRF protection)
const storedState = await sessionStore.get(`oauth_state_${state}`)
if (!storedState) throw new Error('Invalid state parameter')
const config = OAUTH_PROVIDERS[provider]
if (!config) throw new Error(`Unknown provider: ${provider}`)
// Exchange code for tokens
const tokens = await exchangeCodeForTokens(code, storedState.verifier, config)
// Fetch user profile from provider
const profile = await fetchUserProfile(config.userInfoEndpoint, tokens.access_token)
// Find or create user (link social identity)
let user = await db.user.findFirst({
where: {
socialAccounts: {
some: { provider, providerUserId: profile.sub }
}
}
})
if (!user) {
user = await db.user.create({
data: {
email: profile.email,
displayName: profile.name,
socialAccounts: {
create: {
provider,
providerUserId: profile.sub,
email: profile.email,
}
}
}
})
}
const session = await createSession(user.id)
return { user, session }
}
```
## Session Management
```typescript
import { SignJWT, jwtVerify } from 'jose'
const SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET!)
async function createSession(userId: string): Promise<string> {
const sessionId = crypto.randomUUID()
// Store session server-side (not just JWT)
await db.session.create({
data: {
id: sessionId,
userId,
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24h
createdAt: new Date(),
}
})
// Issue signed session token
const token = await new SignJWT({ sub: userId, sid: sessionId })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('24h')
.sign(SESSION_SECRET)
return token
}
// Set session cookie (httpOnly, secure, sameSite)
function setSessionCookie(res: Response, token: string): void {
res.cookie('session', token, {
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only
sameSite: 'lax', // CSRF protection
maxAge: 24 * 60 * 60 * 1000,
path: '/',
domain: '.example.com',
})
}
// Validate session middleware
async function validateSession(req: Request, res: Response, next: NextFunction) {
const token = req.cookies.session
if (!token) return res.status(401).json({ error: 'No session' })
try {
const { payload } = await jwtVerify(token, SESSION_SECRET)
const session = await db.session.findUnique({ where: { id: payload.sid as string } })
if (!session || session.expiresAt < new Date()) {
return res.status(401).json({ error: 'Session expired' })
}
req.user = { id: payload.sub as string, sessionId: session.id }
next()
} catch {
return res.status(401).json({ error: 'Invalid session' })
}
}
```
## Checklist
- [ ] PKCE enabled for all public clients (SPA, mobile, CLI)
- [ ] State parameter validated on callback (CSRF protection)
- [ ] Tokens stored in httpOnly secure cookies (not localStorage)
- [ ] Refresh tokens rotated on each use (detect token theft)
- [ ] Session validated server-side (not just JWT expiry)
- [ ] Social login links to internal user account (not provider-dependent)
- [ ] Nonce in OIDC requests for replay protection
- [ ] Logout: revoke tokens + clear session + clear cookies
## Anti-Patterns
- Storing tokens in localStorage (XSS exposes all tokens)
- Implicit grant flow (deprecated, tokens in URL fragment)
- Not validating state parameter: vulnerable to CSRF
- Long-lived access tokens without refresh mechanism
- Trusting JWT without server-side session validation (no revocation)
- Hardcoding client secrets in frontend code (use backend proxy)Related Skills
websocket-patterns
Connection management, room patterns, reconnection strategies, message buffering, and binary protocol design.
vector-db-patterns
Embedding strategies, ANN algorithms, hybrid search, RAG chunking strategies, and reranking for semantic search and retrieval.
tracing-patterns
OpenTelemetry setup, span context propagation, sampling strategies, Jaeger queries
terraform-patterns
Module composition, state management, workspace strategy, provider versioning, and infrastructure-as-code best practices.
swift-patterns
SwiftUI view composition, @Observable patterns, async/await concurrency, TCA architecture, and Combine reactive streams.
springboot-patterns
Spring Boot architecture patterns, REST API design, layered services, data access, caching, async processing, and logging. Use for Java Spring Boot backend work.
seo-patterns
Meta tag patterns, structured data (JSON-LD), Core Web Vitals optimization, and SSR/SSG strategies for search visibility.
secret-patterns
30+ service-specific secret detection regex patterns, entropy-based detection, PEM/JWT/Base64 identification, and false positive filtering.
saas-payment-patterns
Payment provider abstraction, webhook security, subscription lifecycle, dunning flows, pricing models, invoicing, tax handling, and refund patterns for SaaS applications.
saas-auth-patterns
SaaS authentication and authorization patterns including JWT vs session strategies, multi-tenant isolation, RBAC, API key management, passwordless flows, MFA, and secure session handling.
saas-analytics-patterns
SaaS analytics event taxonomy, metric formulas (MRR, churn, LTV), provider-agnostic tracking, funnel analysis, cohort setup, and privacy-respecting instrumentation.
revenuecat-patterns
RevenueCat SDK entegrasyon pattern'leri. iOS (Swift), Android (Kotlin), React Native ve Flutter icin setup, offerings, entitlement checking, webhook integration, StoreKit 2 migration ve sandbox testing.