OAuth 2.1 compliant authentication flows (MANDATORY Q2 2026). PKCE required for ALL clients, Implicit Flow removed, modern token security.
response_type=token; tokens returned in URL fragments leak via browser history, referrer headers, and server logs. Migrate immediately to Authorization Code + PKCE.| Anti-Pattern | Why It Fails | Correct Approach |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Storing tokens in localStorage | Exposed to XSS; any script on the page (including third-party) can read and exfiltrate | Use HttpOnly cookies set server-side after token exchange |
| Using Implicit Flow (response_type=token) | Tokens in URL fragments leak via browser history, Referer headers, and proxy/CDN logs | Use Authorization Code Flow with PKCE |
| Collecting user passwords directly (Resource Owner Password Credentials) | Violates OAuth separation of concerns; client handles credentials it should never see | Use Authorization Code Flow; direct users to the authorization server login page |
| Wildcard or partial redirect URI matching | Open redirect attack; adversary registers https://evil.com which prefix-matches a wildcard | Register exact URIs; server rejects any URI not in the pre-approved list |
| Long-lived access tokens (>15 min) without rotation | Token theft window is unbounded; compromised token grants long-term access | Keep access tokens ≤15 min; implement refresh token rotation with reuse detection |
Step 1: Generate PKCE Challenge
// Client-side: Generate code_verifier (43-128 chars, URL-safe)
async function generatePKCE() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
const verifier = base64UrlEncode(array);
// Hash verifier to create challenge
const encoder = new TextEncoder();
const hash = await crypto.subtle.digest('SHA-256', encoder.encode(verifier));
const challenge = base64UrlEncode(new Uint8Array(hash));
return { verifier, challenge };
}
Step 2: Authorization Request
const { verifier, challenge } = await generatePKCE();
sessionStorage.setItem('pkce_verifier', verifier); // Temporary storage only
const authUrl = new URL(authorizationEndpoint);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', clientId);
authUrl.searchParams.set('redirect_uri', redirectUri); // MUST match exactly
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256'); // SHA-256 required
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', cryptoRandomState); // CSRF protection
window.location.href = authUrl.toString();
Step 3: Token Exchange with Code Verifier
const response = await fetch(tokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
code_verifier: sessionStorage.getItem('pkce_verifier'), // Prove possession
client_id: clientId,
redirect_uri: redirectUri, // MUST match authorization request
}),
});
// Clear verifier immediately after use
sessionStorage.removeItem('pkce_verifier');
Token Lifetimes (RFC 8725)
Token Storage (CRITICAL)
// ✅ CORRECT: HttpOnly, Secure, SameSite cookies
// Server sets cookies after token exchange
res.cookie('access_token', accessToken, {
httpOnly: true, // Prevents XSS access
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 15 * 60 * 1000, // 15 minutes
});
res.cookie('refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/auth/refresh', // Limit scope
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days, rotate frequently
});
// ❌ WRONG: NEVER store tokens in localStorage or sessionStorage
localStorage.setItem('token', accessToken); // VULNERABLE TO XSS
sessionStorage.setItem('token', accessToken); // VULNERABLE TO XSS
Refresh Token Rotation
// Server-side: Rotate refresh tokens on every use
async function refreshTokens(oldRefreshToken) {
// Validate old token
const userId = await validateRefreshToken(oldRefreshToken);
// Detect token reuse (possible theft)
if (await isTokenAlreadyUsed(oldRefreshToken)) {
await revokeAllTokensForUser(userId); // Kill all sessions
throw new Error('Token reuse detected - all sessions revoked');
}
// Mark old token as used BEFORE issuing new one
await markTokenAsUsed(oldRefreshToken);
// Issue new tokens
const newAccessToken = generateAccessToken(userId, '15m');
const newRefreshToken = generateRefreshToken(userId);
return { newAccessToken, newRefreshToken };
}
❌ Implicit Flow (REMOVED in OAuth 2.1)
// NEVER DO THIS - Implicit Flow is REMOVED
authUrl.searchParams.set('response_type', 'token'); // ❌ FORBIDDEN
// Tokens in URL fragments leak via browser history, referrer headers, logs
Migration Path: Use Authorization Code Flow + PKCE instead.
❌ Resource Owner Password Credentials (REMOVED)
// NEVER DO THIS - Collecting passwords directly violates OAuth
fetch(tokenEndpoint, {
body: new URLSearchParams({
grant_type: 'password', // ❌ FORBIDDEN
username: user.email,
password: user.password,
}),
});
Migration Path: Use Authorization Code Flow for user auth, Client Credentials for service accounts.
Passkeys/WebAuthn (Recommended for 2026+)
// Register passkey
const credential = await navigator.credentials.create({
publicKey: {
challenge: serverChallenge,
rp: { name: 'Your App' },
user: {
id: userIdBytes,
name: user.email,
displayName: user.name,
},
pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
authenticatorSelection: {
authenticatorAttachment: 'platform', // Device-bound
userVerification: 'required',
},
},
});
// Authenticate with passkey
const assertion = await navigator.credentials.get({
publicKey: {
challenge: serverChallenge,
rpId: 'yourapp.com',
userVerification: 'required',
},
});
Before deploying:
Login with OAuth 2.1 (email/password)
// Use Authorization Code Flow with PKCE even for first-party login
async function login(email: string, password: string) {
// 1. Start PKCE flow
const { verifier, challenge } = await generatePKCE();
sessionStorage.setItem('pkce_verifier', verifier);
// 2. POST credentials to your auth server's login endpoint
const response = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password, code_challenge: challenge }),
});
// 3. Server validates credentials, returns authorization code
const { code } = await response.json();
// 4. Exchange code for tokens with verifier
await exchangeCodeForTokens(code);
}
Login with GitHub OAuth
async function loginWithGitHub() {
const { verifier, challenge } = await generatePKCE();
sessionStorage.setItem('pkce_verifier', verifier);
const authUrl = new URL('https://github.com/login/oauth/authorize');
authUrl.searchParams.set('client_id', GITHUB_CLIENT_ID);
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/auth/callback');
authUrl.searchParams.set('scope', 'user:email');
authUrl.searchParams.set('state', generateState());
// Note: GitHub doesn't support PKCE yet, but use it when available
window.location.href = authUrl.toString();
}
Logout
async function logout() {
// 1. Clear client-side cookies
document.cookie = 'access_token=; Max-Age=0; path=/';
document.cookie = 'refresh_token=; Max-Age=0; path=/';
// 2. Revoke tokens on server
await fetch('/auth/logout', { method: 'POST' });
// 3. Redirect to login
window.location.href = '/login';
}
</instructions>
<examples>
Example usage:
```
User: "Review this code for authentication flow rules compliance"
Agent: [Analyzes code against guidelines and provides specific feedback]
```
</examples>
Before starting:
cat .claude/context/memory/learnings.md
After completing: Record any new patterns or exceptions discovered.
ASSUME INTERRUPTION: Your context may reset. If it's not in memory, it didn't happen.
npx skills add oimiragieo/authentication-flow-rules下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Category:other