authentication-flow-rules

📁 oimiragieo/agent-studio 📅 Jan 27, 2026
10
总安装量
2
周安装量
#29313
全站排名
安装命令
npx skills add https://github.com/oimiragieo/agent-studio --skill authentication-flow-rules

Agent 安装分布

clawdbot 1
github-copilot 1

Skill 文档

Authentication Flow Rules Skill

CRITICAL: Required Changes from OAuth 2.0

  • PKCE is REQUIRED for ALL clients (public AND confidential)
  • Implicit Flow is REMOVED – do not use, migrate immediately
  • Resource Owner Password Credentials REMOVED – never collect user passwords directly
  • Bearer tokens in URI query parameters FORBIDDEN – tokens only in Authorization headers or POST bodies
  • Exact redirect URI matching REQUIRED – no wildcards, no partial matches

Authorization Code Flow with PKCE (The ONLY User Flow)

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 Security Best Practices

Token Lifetimes (RFC 8725)

  • Access tokens: ≤15 minutes maximum
  • Refresh tokens: Rotate on every use (sender-constrained or rotation required)
  • ID tokens: Short-lived, validate signature and claims

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 };
}

REMOVED Flows (Do Not Use)

❌ 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.

Modern Authentication Patterns

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',
  },
});

Security Checklist

Before deploying:

  • PKCE enabled for ALL clients (no exceptions)
  • Implicit Flow completely removed/disabled
  • Password Credentials Flow removed/disabled
  • Exact redirect URI matching enforced (no wildcards)
  • Tokens NEVER in URL query parameters
  • Access tokens ≤15 minutes
  • Refresh token rotation enabled with reuse detection
  • Tokens stored in HttpOnly, Secure, SameSite=Strict cookies
  • All token transport over HTTPS only
  • State parameter used for CSRF protection
  • Token signature validation (JWT RS256 or ES256, not HS256)

Common Implementation Patterns

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';
}

Memory Protocol (MANDATORY)

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.