agentsclimarketplace

Authentication patterns

Skill yigityildiz0/universal-ai-skill-library/skills/common/authentication-patterns

531 searchable AI Agent Skills for Claude Code, OpenAI Codex, and OpenCode — EN/TR catalog, platform and risk notes, direct ZIPs, and curated bundles.

Install
npx -y skills add yigityildiz0/universal-ai-skill-library --skill authentication-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

3 things to look at

  • 18 days oldThe repository was created 18 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Authentication and authorization patterns including OAuth 2.0, OIDC, JWT, session management, MFA, and passkeys. Use when implementing login flows, securing.

SKILL.md

30.3 KB, ~6.9k tokens by cl100k_base, as published. Nobody here has run it

Authentication Patterns

Comprehensive guidance for implementing secure authentication and authorization systems. Covers OAuth 2.0 flows, OpenID Connect, JWT lifecycle management, session security, password hashing, multi-factor authentication, passkeys/WebAuthn, role-based and attribute-based access control, API key management, and common vulnerability prevention.

When to Use This Skill

Use this skill for:

  • Implementing OAuth 2.0 authorization code flow with PKCE
  • Integrating OpenID Connect for SSO (Single Sign-On)
  • Designing JWT issuance, validation, and refresh token rotation
  • Building secure session management with cookies or tokens
  • Implementing password hashing with bcrypt or Argon2
  • Adding MFA (TOTP, WebAuthn/passkeys) to an application
  • Designing RBAC or ABAC authorization models
  • Managing API keys for service-to-service communication
  • Configuring security headers (CSP, CORS, HSTS)
  • Reviewing auth architecture for common vulnerabilities

Trigger phrases: "authentication", "authorization", "OAuth", "OIDC", "JWT", "session management", "login flow", "MFA", "passkeys", "WebAuthn", "RBAC", "ABAC", "API key", "CORS", "CSRF", "password hashing", "refresh token"

What This Skill Does

Provides production-grade authentication patterns including:

  • OAuth 2.0: Authorization code + PKCE, client credentials, device flow
  • OpenID Connect: ID tokens, userinfo endpoint, discovery
  • JWT: Structure, signing algorithms, validation, refresh rotation
  • Sessions: Cookie security, token storage, session fixation prevention
  • Passwords: Hashing (bcrypt, Argon2), salting, migration strategies
  • MFA: TOTP setup, WebAuthn/passkeys registration and assertion
  • Access Control: RBAC middleware, ABAC policies, permission models
  • API Security: API key management, rate limiting, scope enforcement
  • Headers: CSP, CORS, HSTS, X-Frame-Options configuration

Instructions

Step 1: Understand OAuth 2.0 Flows

Flow Selection Guide:

FlowUse CaseClient Type
Authorization Code + PKCEWeb apps, SPAs, mobilePublic clients
Client CredentialsMachine-to-machineConfidential clients
Device AuthorizationSmart TVs, CLI toolsInput-constrained devices
Refresh TokenExtend sessions without re-authAny client with refresh grant

Authorization Code Flow with PKCE (recommended for all user-facing apps):

┌──────┐     1. Auth Request + code_verifier     ┌──────────────┐
│      │────────────────────────────────────────▶│              │
│      │                                         │ Authorization│
│ App  │◀────────────────────────────────────────│   Server     │
│      │     2. Authorization Code               │              │
│      │                                         └──────┬───────┘
│      │     3. Token Request + code_verifier            │
│      │────────────────────────────────────────▶        │
│      │                                                 │
│      │◀────────────────────────────────────────        │
│      │     4. Access Token + Refresh Token             │
└──────┘                                         ┌──────┴───────┐
   │                                             │   Resource   │
   │         5. API Request + Access Token       │   Server     │
   │────────────────────────────────────────────▶│              │
   │                                             └──────────────┘

PKCE Implementation (Node.js):

import crypto from 'crypto';

// Step 1: Generate PKCE code verifier and challenge
function generatePKCE(): { verifier: string; challenge: string } {
  // Code verifier: 43-128 character random string
  const verifier = crypto.randomBytes(32).toString('base64url');

  // Code challenge: SHA-256 hash of verifier, base64url encoded
  const challenge = crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');

  return { verifier, challenge };
}

// Step 2: Build authorization URL
function buildAuthUrl(clientId: string, redirectUri: string, challenge: string): string {
  const params = new URLSearchParams({
    response_type: 'code',
    client_id: clientId,
    redirect_uri: redirectUri,
    scope: 'openid profile email',
    state: crypto.randomBytes(16).toString('hex'),  // CSRF protection
    code_challenge: challenge,
    code_challenge_method: 'S256',
  });
  return `https://auth.example.com/authorize?${params}`;
}

// Step 3: Exchange authorization code for tokens
async function exchangeCode(
  code: string,
  verifier: string,
  clientId: string,
  redirectUri: string
): Promise<TokenResponse> {
  const response = await fetch('https://auth.example.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: redirectUri,
      client_id: clientId,
      code_verifier: verifier,  // Prove we initiated the request
    }),
  });

  if (!response.ok) {
    throw new Error(`Token exchange failed: ${response.status}`);
  }

  return response.json();
}

interface TokenResponse {
  access_token: string;
  refresh_token: string;
  id_token: string;     // Present with OIDC
  token_type: 'Bearer';
  expires_in: number;
}

Client Credentials Flow (service-to-service):

async function getServiceToken(clientId: string, clientSecret: string): Promise<string> {
  const response = await fetch('https://auth.example.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`,
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      scope: 'orders:read orders:write',
    }),
  });

  const data = await response.json();
  return data.access_token;
}

Step 2: Implement JWT Lifecycle

JWT Structure:

Header.Payload.Signature

Header:  { "alg": "RS256", "typ": "JWT", "kid": "key-2026-03" }
Payload: { "sub": "user-123", "iss": "https://auth.example.com",
           "aud": "https://api.example.com", "exp": 1709500800,
           "iat": 1709497200, "scope": "read write" }
Signature: RS256(base64url(header) + "." + base64url(payload), privateKey)

JWT Signing and Validation (Node.js):

import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

// ── Token Creation (auth server side) ─────────────────────────────

const PRIVATE_KEY = process.env.JWT_PRIVATE_KEY!;

function createAccessToken(userId: string, scopes: string[]): string {
  return jwt.sign(
    {
      sub: userId,
      scope: scopes.join(' '),
    },
    PRIVATE_KEY,
    {
      algorithm: 'RS256',
      issuer: 'https://auth.example.com',
      audience: 'https://api.example.com',
      expiresIn: '15m',   // Short-lived access tokens
      keyid: 'key-2026-03',
    }
  );
}

function createRefreshToken(userId: string, tokenFamily: string): string {
  return jwt.sign(
    {
      sub: userId,
      family: tokenFamily,  // For rotation detection
    },
    PRIVATE_KEY,
    {
      algorithm: 'RS256',
      issuer: 'https://auth.example.com',
      expiresIn: '7d',
      jwtid: crypto.randomUUID(),  // Unique token ID for revocation
    }
  );
}

// ── Token Validation (resource server side) ───────────────────────

const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 5,
});

function getSigningKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback): void {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key?.getPublicKey());
  });
}

function validateAccessToken(token: string): Promise<jwt.JwtPayload> {
  return new Promise((resolve, reject) => {
    jwt.verify(
      token,
      getSigningKey,
      {
        algorithms: ['RS256'],
        issuer: 'https://auth.example.com',
        audience: 'https://api.example.com',
        clockTolerance: 30,  // 30-second clock skew tolerance
      },
      (err, decoded) => {
        if (err) return reject(err);
        resolve(decoded as jwt.JwtPayload);
      }
    );
  });
}

Refresh Token Rotation (prevents token theft):

async function refreshTokens(refreshToken: string): Promise<TokenResponse> {
  // 1. Validate the refresh token
  const decoded = await validateRefreshToken(refreshToken);

  // 2. Check if token has been revoked (detect reuse attacks)
  const isRevoked = await tokenStore.isRevoked(decoded.jti);
  if (isRevoked) {
    // Token reuse detected: revoke the entire token family
    await tokenStore.revokeFamily(decoded.family);
    throw new Error('Refresh token reuse detected. All sessions revoked.');
  }

  // 3. Revoke the old refresh token
  await tokenStore.revoke(decoded.jti);

  // 4. Issue new token pair
  const newAccessToken = createAccessToken(decoded.sub, decoded.scope);
  const newRefreshToken = createRefreshToken(decoded.sub, decoded.family);

  return {
    access_token: newAccessToken,
    refresh_token: newRefreshToken,
    token_type: 'Bearer',
    expires_in: 900,
  };
}

Step 3: Secure Session Management

Cookie-Based Sessions (Express.js):

import express from 'express';
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

const app = express();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  name: '__Host-session',           // __Host- prefix enforces Secure + no Domain
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  rolling: true,                    // Reset expiry on each request
  cookie: {
    secure: true,                   // HTTPS only
    httpOnly: true,                 // Not accessible via JavaScript
    sameSite: 'lax',                // CSRF protection
    maxAge: 30 * 60 * 1000,        // 30 minutes
    path: '/',
  },
}));

// Session fixation prevention: regenerate session ID after login
app.post('/login', async (req, res) => {
  const user = await authenticateUser(req.body.email, req.body.password);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  // Regenerate session to prevent fixation attacks
  req.session.regenerate((err) => {
    if (err) return res.status(500).json({ error: 'Session error' });
    req.session.userId = user.id;
    req.session.role = user.role;
    res.json({ message: 'Logged in' });
  });
});

// Logout: destroy session and clear cookie
app.post('/logout', (req, res) => {
  req.session.destroy(() => {
    res.clearCookie('__Host-session');
    res.json({ message: 'Logged out' });
  });
});

Token Storage in SPAs (security comparison):

StorageXSS RiskCSRF RiskRecommendation
localStorageHigh (JS accessible)NoneAvoid for tokens
sessionStorageHigh (JS accessible)NoneAvoid for tokens
HttpOnly cookieNone (not JS accessible)MediumPreferred with SameSite
In-memory variableLow (lost on refresh)NoneGood for access tokens

Step 4: Implement Password Hashing

import bcrypt from 'bcrypt';
import argon2 from 'argon2';

// ── bcrypt (widely supported, proven) ─────────────────────────────

const BCRYPT_ROUNDS = 12;  // Aim for ~250ms hash time

async function hashPasswordBcrypt(password: string): Promise<string> {
  return bcrypt.hash(password, BCRYPT_ROUNDS);
}

async function verifyPasswordBcrypt(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// ── Argon2id (recommended for new projects, OWASP preferred) ──────

async function hashPasswordArgon2(password: string): Promise<string> {
  return argon2.hash(password, {
    type: argon2.argon2id,    // Hybrid: resistant to side-channel + GPU attacks
    memoryCost: 65536,        // 64 MB
    timeCost: 3,              // 3 iterations
    parallelism: 4,           // 4 threads
  });
}

async function verifyPasswordArgon2(password: string, hash: string): Promise<boolean> {
  return argon2.verify(hash, password);
}

// ── Password policy enforcement ───────────────────────────────────

function validatePasswordPolicy(password: string): string[] {
  const errors: string[] = [];
  if (password.length < 12) errors.push('Minimum 12 characters');
  if (password.length > 128) errors.push('Maximum 128 characters');
  // Check against common breached passwords (use a bloom filter or k-anonymity API)
  // Do NOT enforce complex character rules (NIST SP 800-63B guidance)
  return errors;
}

Step 5: Add Multi-Factor Authentication

TOTP Implementation (Google Authenticator compatible):

import { authenticator } from 'otplib';
import QRCode from 'qrcode';

// ── TOTP Setup ────────────────────────────────────────────────────

async function enableTOTP(userId: string, email: string) {
  // Generate a secret for this user
  const secret = authenticator.generateSecret(20);  // 160-bit secret

  // Store secret (encrypted) in database, marked as unverified
  await db.users.update(userId, {
    totp_secret: encrypt(secret),
    totp_verified: false,
  });

  // Generate QR code for authenticator app
  const otpauthUrl = authenticator.keyuri(email, 'MyApp', secret);
  const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);

  return {
    secret,        // Show to user as backup
    qrCode: qrCodeDataUrl,
  };
}

// ── TOTP Verification ─────────────────────────────────────────────

async function verifyTOTP(userId: string, token: string): Promise<boolean> {
  const user = await db.users.findById(userId);
  const secret = decrypt(user.totp_secret);

  // Validate with a 1-step window (allows 30s clock drift)
  const isValid = authenticator.check(token, secret);

  if (isValid && !user.totp_verified) {
    await db.users.update(userId, { totp_verified: true });
  }

  return isValid;
}

WebAuthn/Passkeys Registration (using SimpleWebAuthn):

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
} from '@simplewebauthn/server';

const RP_NAME = 'MyApp';
const RP_ID = 'example.com';
const ORIGIN = 'https://example.com';

// ── Passkey Registration ──────────────────────────────────────────

async function startPasskeyRegistration(userId: string, email: string) {
  const existingCredentials = await db.credentials.findByUser(userId);

  const options = await generateRegistrationOptions({
    rpName: RP_NAME,
    rpID: RP_ID,
    userName: email,
    userDisplayName: email,
    attestationType: 'none',       // Skip attestation for simpler flow
    excludeCredentials: existingCredentials.map(cred => ({
      id: cred.credentialId,
      type: 'public-key',
    })),
    authenticatorSelection: {
      residentKey: 'preferred',    // Enable discoverable credentials (passkeys)
      userVerification: 'preferred',
    },
  });

  // Store challenge in session for verification
  await sessionStore.set(userId, { challenge: options.challenge });

  return options;
}

async function finishPasskeyRegistration(userId: string, response: any) {
  const session = await sessionStore.get(userId);

  const verification = await verifyRegistrationResponse({
    response,
    expectedChallenge: session.challenge,
    expectedOrigin: ORIGIN,
    expectedRPID: RP_ID,
  });

  if (verification.verified && verification.registrationInfo) {
    await db.credentials.create({
      userId,
      credentialId: verification.registrationInfo.credentialID,
      publicKey: verification.registrationInfo.credentialPublicKey,
      counter: verification.registrationInfo.counter,
    });
  }

  return verification.verified;
}

Step 6: Implement Role-Based Access Control (RBAC)

RBAC Middleware (Express.js):

// ── Permission Model ──────────────────────────────────────────────

interface Role {
  name: string;
  permissions: string[];
}

const ROLES: Record<string, Role> = {
  admin: {
    name: 'admin',
    permissions: ['users:read', 'users:write', 'users:delete',
                  'orders:read', 'orders:write', 'orders:delete',
                  'reports:read', 'settings:write'],
  },
  manager: {
    name: 'manager',
    permissions: ['users:read', 'orders:read', 'orders:write', 'reports:read'],
  },
  viewer: {
    name: 'viewer',
    permissions: ['orders:read', 'reports:read'],
  },
};

// ── Authorization Middleware ──────────────────────────────────────

function requirePermission(...requiredPermissions: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userRole = req.session?.role;

    if (!userRole) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    const role = ROLES[userRole];
    if (!role) {
      return res.status(403).json({ error: 'Unknown role' });
    }

    const hasPermission = requiredPermissions.every(
      perm => role.permissions.includes(perm)
    );

    if (!hasPermission) {
      return res.status(403).json({
        error: 'Insufficient permissions',
        required: requiredPermissions,
      });
    }

    next();
  };
}

// ── Route Usage ───────────────────────────────────────────────────

app.get('/api/orders', requirePermission('orders:read'), getOrders);
app.post('/api/orders', requirePermission('orders:write'), createOrder);
app.delete('/api/orders/:id', requirePermission('orders:delete'), deleteOrder);
app.get('/api/reports', requirePermission('reports:read'), getReports);
app.delete('/api/users/:id', requirePermission('users:delete'), deleteUser);

Attribute-Based Access Control (ABAC) Example:

interface PolicyContext {
  user: { id: string; role: string; department: string };
  resource: { ownerId: string; type: string; classification: string };
  action: string;
  environment: { time: Date; ipAddress: string };
}

function evaluatePolicy(ctx: PolicyContext): boolean {
  const policies = [
    // Admins can do anything
    (c: PolicyContext) => c.user.role === 'admin',

    // Users can read their own resources
    (c: PolicyContext) =>
      c.action === 'read' && c.resource.ownerId === c.user.id,

    // Managers can write resources in their department
    (c: PolicyContext) =>
      c.user.role === 'manager' &&
      c.action === 'write' &&
      c.resource.classification !== 'confidential',

    // No access to confidential resources outside business hours
    (c: PolicyContext) => {
      if (c.resource.classification === 'confidential') {
        const hour = c.environment.time.getHours();
        return hour >= 8 && hour <= 18;
      }
      return true;
    },
  ];

  return policies.some(policy => policy(ctx));
}

Step 7: Configure Security Headers

import helmet from 'helmet';

app.use(helmet({
  // Content Security Policy: prevent XSS and data injection
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'strict-dynamic'"],
      styleSrc: ["'self'", "'unsafe-inline'"],  // Consider nonces for stricter CSP
      imgSrc: ["'self'", "data:", "https://cdn.example.com"],
      connectSrc: ["'self'", "https://api.example.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],          // Prevent clickjacking
      baseUri: ["'self'"],
      formAction: ["'self'"],
      upgradeInsecureRequests: [],
    },
  },

  // HSTS: force HTTPS for 1 year including subdomains
  strictTransportSecurity: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },

  // Prevent MIME type sniffing
  xContentTypeOptions: true,   // X-Content-Type-Options: nosniff

  // Referrer policy
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },

  // Permissions policy
  permittedCrossDomainPolicies: { permittedPolicies: 'none' },
}));

// ── CORS Configuration ────────────────────────────────────────────

import cors from 'cors';

app.use(cors({
  origin: ['https://app.example.com', 'https://admin.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,             // Allow cookies in cross-origin requests
  maxAge: 86400,                 // Cache preflight for 24 hours
}));

Step 8: Prevent Common Vulnerabilities

CSRF Protection:

import csrf from 'csurf';

// For cookie-based sessions, use double-submit cookie pattern
app.use(csrf({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
  },
}));

// Include CSRF token in responses for forms/SPAs
app.get('/api/csrf-token', (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

Session Fixation Prevention:

// Always regenerate session ID after authentication state changes
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  // Destroy old session and create new one
  const oldSession = { ...req.session };
  req.session.regenerate((err) => {
    if (err) return res.status(500).json({ error: 'Session error' });
    // Copy non-sensitive data from old session if needed
    req.session.userId = user.id;
    req.session.role = user.role;
    res.json({ message: 'Authenticated' });
  });
});

Token Theft Mitigation:

// Bind tokens to client fingerprint
function createBoundToken(userId: string, clientFingerprint: string): string {
  return jwt.sign(
    {
      sub: userId,
      fpt: crypto.createHash('sha256').update(clientFingerprint).digest('hex'),
    },
    PRIVATE_KEY,
    { algorithm: 'RS256', expiresIn: '15m' }
  );
}

// Validate fingerprint on each request
function validateBoundToken(token: string, clientFingerprint: string): boolean {
  const decoded = jwt.verify(token, PUBLIC_KEY) as any;
  const expectedFpt = crypto.createHash('sha256').update(clientFingerprint).digest('hex');
  return decoded.fpt === expectedFpt;
}

Best Practices

  • Use PKCE for all OAuth flows: Even confidential clients benefit from PKCE as defense-in-depth
  • Short-lived access tokens: 5-15 minutes; use refresh tokens for longer sessions
  • Rotate refresh tokens: Issue a new refresh token on every use; detect reuse attacks
  • Store tokens in HttpOnly cookies: Not localStorage or sessionStorage
  • Use Argon2id for passwords: OWASP-recommended; bcrypt is acceptable but Argon2id is stronger
  • Never store plaintext passwords: This rule has no exceptions
  • Regenerate session IDs: After login, logout, privilege escalation, and periodically
  • Implement rate limiting on auth endpoints: Prevent brute-force attacks
  • Log all authentication events: Successful logins, failures, MFA challenges, token refreshes
  • Use SameSite cookies: Set to Lax or Strict to prevent CSRF
  • Validate JWT claims thoroughly: Always check iss, aud, exp, and nbf
  • Use asymmetric signing (RS256): Allows resource servers to validate without the signing key

Common Patterns

Pattern 1: API Key with Rate Limiting

// API key middleware with per-key rate limiting
const rateLimiter = new Map<string, { count: number; resetAt: number }>();

function apiKeyAuth(req: Request, res: Response, next: NextFunction) {
  const apiKey = req.headers['x-api-key'] as string;
  if (!apiKey) return res.status(401).json({ error: 'API key required' });

  // Validate key and get associated client
  const client = await db.apiKeys.findByKey(hashApiKey(apiKey));
  if (!client) return res.status(401).json({ error: 'Invalid API key' });

  // Rate limiting per key
  const now = Date.now();
  const limit = rateLimiter.get(client.id) || { count: 0, resetAt: now + 60000 };
  if (now > limit.resetAt) {
    limit.count = 0;
    limit.resetAt = now + 60000;
  }
  limit.count++;
  rateLimiter.set(client.id, limit);

  if (limit.count > client.rateLimit) {
    return res.status(429).json({ error: 'Rate limit exceeded' });
  }

  req.client = client;
  next();
}

Pattern 2: Scope-Based Authorization for APIs

function requireScope(...requiredScopes: string[]) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    if (!token) return res.status(401).json({ error: 'Token required' });

    try {
      const decoded = await validateAccessToken(token);
      const tokenScopes = (decoded.scope || '').split(' ');

      const hasScope = requiredScopes.every(s => tokenScopes.includes(s));
      if (!hasScope) {
        return res.status(403).json({
          error: 'insufficient_scope',
          required: requiredScopes,
          provided: tokenScopes,
        });
      }

      req.user = decoded;
      next();
    } catch (err) {
      return res.status(401).json({ error: 'Invalid token' });
    }
  };
}

// Usage
app.get('/api/orders', requireScope('orders:read'), getOrders);
app.post('/api/orders', requireScope('orders:write'), createOrder);

Quality Checklist

  • OAuth 2.0 flow uses PKCE (even for confidential clients)
  • Access tokens are short-lived (5-15 minutes)
  • Refresh token rotation implemented with reuse detection
  • Passwords hashed with Argon2id or bcrypt (cost factor >= 12)
  • Session IDs regenerated after authentication state changes
  • Cookies use Secure, HttpOnly, SameSite attributes
  • CSRF protection implemented for cookie-based auth
  • JWT validation checks iss, aud, exp, and algorithm
  • MFA available for all user accounts
  • Security headers configured (CSP, HSTS, CORS)
  • Rate limiting applied to login and token endpoints
  • Authentication events logged for audit trail
  • API keys are hashed before storage (never stored in plaintext)

Common Rationalizations

RationalizationReality
"We can skip PKCE because our client is confidential"Authorization code interception attacks (via open redirects or referrer headers) are possible even with confidential clients; PKCE prevents code replay at zero implementation cost.
"JWTs are stateless so we don't need refresh token rotation"Without rotation, a stolen refresh token grants indefinite access until expiry; rotation with reuse detection (family-based revocation) limits the window to a single use, as demonstrated by the approach recommended after the Auth0 token reuse incidents.
"bcrypt with cost 10 is fine for new projects"Cost 10 was calibrated for ~2012 hardware; modern GPUs can test billions of candidates per second — cost 12 or Argon2id is the current OWASP minimum.
"We store access tokens in localStorage because it's simpler"XSS in any third-party script on the page (analytics, chat widgets) can exfiltrate localStorage tokens silently; HttpOnly cookies are immune to JavaScript access.
"Session ID regeneration after login is optional"Session fixation allows an attacker to pre-set a known session ID, then hijack it after the victim authenticates — a P0 vulnerability with trivial exploitation.
"We check authorization at the route level, which is sufficient"Route-level checks prevent accessing the wrong endpoint; IDOR exploits occur at the data layer when a user passes a valid endpoint but with another user's resource ID, bypassing route guards entirely.

Verification

  • OAuth 2.0 flows use PKCE (code_challenge_method: S256) confirmed in code or config
  • Refresh token rotation is implemented and reuse detection revokes the token family on replay
  • Password hashing uses Argon2id or bcrypt with cost >= 12 (verified in source, not just docs)
  • All session cookies have Secure, HttpOnly, and SameSite attributes set in code
  • JWT validation explicitly checks iss, aud, exp, and alg — no none algorithm accepted
  • Rate limiting is applied to the login endpoint (verified by attempting >10 requests/minute)

Related Skills

  • security-review - Application security assessment including auth review
  • pre-commit-checklist - Security checks before committing auth code
  • dependency-security-audit - Auditing auth library vulnerabilities
  • cicd-architect - Securing CI/CD pipelines with service accounts and tokens

Version: 1.0.0 Last Updated: March 2026

Iterative Refinement Strategy

This skill is optimized for an iterative approach:

  1. Execute: Perform the core steps defined above.
  2. Review: Critically analyze the output (coverage, quality, completeness).
  3. Refine: If targets aren't met, repeat the specific implementation steps with improved context.
  4. Loop: Continue until the definition of done is satisfied.

Gives 1 of the 12 instructions most auth identity skills give in ~6.9k tokens

Counted across 409 of the 410 authors here whose files we hold, read 2026-08-06

  • hash passwords with bcrypt or argon2here, and in 53 of 409, across 43 files
  • use parameterized queriesin 47 of 409, across 39 files
  • load SECRET_KEY from environment variablesin 23 of 409, across 14 files
  • validate all input server-sidein 19 of 409, across 11 files
  • refresh access tokens before expiryin 17 of 409, across 9 files
  • store tokens in httponly cookiesin 17 of 409, across 16 files
  • store refresh tokens securelyin 16 of 409, across 6 files
  • validate webhook signatures before processingin 15 of 409, across 5 files
  • sanitize user inputsin 15 of 409, across 9 files
  • implement rate limiting on auth endpointsin 14 of 409, across 9 files
  • encrypt sensitive data at restin 13 of 409, across 10 files
  • validate uploaded file extensions and sizesin 12 of 409, across 5 files

Said here and by no other author read

  • use client credentials flow for service-to-service

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.