Skip to content
Development
Skill

/auth-security

OAuth 2.1 + JWT authentication security best practices. Use when implementing auth, API authorization, token management. Follows RFC 9700 (2025).

From plugin
spellbook
289111 skills7 agents2 commands

Claude Code

$ npx -y skills add majiayu000/spellbook --skill auth-security --agent claude-code

SKILL.md

auth-security.SKILL.md
name: auth-security
description: OAuth 2.1 + JWT authentication security best practices. Use when implementing auth, API authorization, token management. Follows RFC 9700 (2025).

Auth Security

Core Principles

  • **OAuth 2.1** — Follow RFC 9700 (January 2025)
  • **PKCE Required** — All clients must use PKCE
  • **Short-lived Tokens** — Access tokens expire in 5-15 minutes
  • **Token Rotation** — Refresh tokens are single-use
  • **HttpOnly Storage** — Browser tokens in HttpOnly cookies
  • **Explicit Algorithm** — Never trust JWT header algorithm
  • **No backwards compatibility** — Delete deprecated auth flows

---

OAuth 2.1 Key Changes

Deprecated Flows (DO NOT USE)

| Flow | Status | Replacement | |------|--------|-------------| | Implicit Grant | Removed | Authorization Code + PKCE | | Password Grant | Removed | Authorization Code + PKCE | | Auth Code without PKCE | Removed | Must use PKCE |

Required: Authorization Code + PKCE

import crypto from 'crypto';

// 1. Generate code verifier (43-128 chars)
function generateCodeVerifier(): string {
  return crypto.randomBytes(32).toString('base64url');
}

// 2. Generate code challenge
function generateCodeChallenge(verifier: string): string {
  return crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');
}

// 3. Authorization request
const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);

const authUrl = new URL('https://auth.example.com/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', generateState());

// 4. Token exchange (after redirect)
const tokenResponse = await fetch('https://auth.example.com/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authorizationCode,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    code_verifier: verifier, // Prove we initiated the request
  }),
});

---

JWT Best Practices

Algorithm Selection (2025)

| Priority | Algorithm | Notes | |----------|-----------|-------| | 1 | EdDSA (Ed25519) | Most secure, quantum-resistant properties | | 2 | ES256 (ECDSA P-256) | Widely supported, compact signatures | | 3 | PS256 (RSA-PSS) | More secure than RS256 | | 4 | RS256 (RSA PKCS#1) | Best compatibility |

// Recommended: ES256
import { SignJWT, jwtVerify } from 'jose';

const privateKey = await importPKCS8(PRIVATE_KEY_PEM, 'ES256');
const publicKey = await importSPKI(PUBLIC_KEY_PEM, 'ES256');

// Sign
const token = await new SignJWT({ sub: userId, scope: 'read write' })
  .setProtectedHeader({ alg: 'ES256', typ: 'JWT', kid: keyId })
  .setIssuer('https://auth.example.com')
  .setAudience('https://api.example.com')
  .setExpirationTime('15m')
  .setIssuedAt()
  .setJti(crypto.randomUUID())
  .sign(privateKey);

Token Structure

interface AccessTokenPayload {
  // Standard claims
  iss: string;  // Issuer
  sub: string;  // Subject (user ID)
  aud: string;  // Audience
  exp: number;  // Expiration (Unix timestamp)
  iat: number;  // Issued at
  jti: string;  // JWT ID (unique identifier)

  // Custom claims
  scope: string;      // Permissions
  email?: string;     // User email
  roles?: string[];   // User roles
}

Verification (Critical)

import { jwtVerify, errors } from 'jose';

async function verifyAccessToken(token: string): Promise<AccessTokenPayload> {
  try {
    const { payload } = await jwtVerify(token, publicKey, {
      // CRITICAL: Explicitly specify allowed algorithms
      algorithms: ['ES256'],

      // Validate standard claims
      issuer: 'https://auth.example.com',
      audience: 'https://api.example.com',

      // Clock tolerance for sync issues
      clockTolerance: 30,
    });

    // Additional validation
    if (!payload.scope?.includes('read')) {
      throw new Error('Insufficient scope');
    }

    return payload as AccessTokenPayload;
  } catch (err) {
    if (err instanceof errors.JWTExpired) {
      throw new AuthError('Token expired', 'TOKEN_EXPIRED');
    }
    if (err instanceof errors.JWTClaimValidationFailed) {
      throw new AuthError('Invalid token claims', 'INVALID_CLAIMS');
    }
    throw new AuthError('Invalid token', 'INVALID_TOKEN');
  }
}

---

Token Storage

Web Applications

// Set token in HttpOnly cookie (server-side)
function setAuthCookie(res: Response, token: string) {
  res.cookie('access_token', token, {
    httpOnly: true,     // Not accessible via JavaScript
    secure: true,       // HTTPS only
    sameSite: 'strict', // CSRF protection
    maxAge: 15 * 60 * 1000, // 15 minutes
    path: '/api',       // Only sent to API routes
  });
}

// Refresh token (longer-lived)
function setRefreshCookie(res: Response, token: string) {
  res.cookie('refresh_token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    path: '/api/auth/refresh',  // Only for refresh endpoint
  });
}

Single Page Applications (SPA)

// Store in memory (NOT localStorage/sessionStorage)
class TokenManager {
  private accessToken: string | null = null;

  setToken(token: string) {
    this.accessToken = token;
  }

  getToken(): string | null {
    return this.accessToken;
  }

  clearToken() {
    this.accessToken = null;
  }
}

// Use with Refresh Token Rotation
// Refresh token in HttpOnly cookie
// Access token in memory

Storage Comparison

| Storage | XSS Safe | CSRF Safe | Persistence | |---------|----------|-----------|-------------| | HttpO

Read more
@majiayu000
Ships withspellbook

Cross-runtime skills for Claude Code, Codex, and multi-agent workflows.

289111 skills7 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
289
Star history of majiayu000/spellbook on GitHub, a chart by star-history.com

Other skills on spellbook.

idea-analogist

idea-analogist

想法群聊室 — 类比者角色。被 idea-team 主编排器调用,或用户单独说"类比一下"、"别的行业有没有"、"yes-and 扩展"、"X 让你想到什么"、"跨界启示"时触发。**专门做跨界类比 + yes-and…

idea-devils-advocate

idea-devils-advocate

想法群聊室 — 反方角色。被 idea-team 主编排器调用,或用户单独说"反方意见"、"挑这个想法的刺"、"为什么会失败"、"找漏洞 / 反例"、"devil's…

idea-research

idea-research

想法群聊室 — 调研员角色。被 idea-team 主编排器调用,或用户单独说"调研一下 X"、"X 的现状/竞品/数据"、"找 2026 数据"、"事实底"时触发。**用 WebSearch 拉真实 2026…

idea-team

idea-team

想法群聊室主持人 — 把一句话想法丢给多角色 AI 团队(调研员/反方/类比者)做查漏补缺。每个角色有自己的 voice,他们互相 @ 接话;你随时插话。**这是创意扩展工具,不打分、不否决、不堵路**。Use when…

idea-to-product

idea-to-product

端到端产品教练 — 把一句话想法走到 PRD + 可点击 HTML 原型。会顶嘴、强制砍功能、用 Nielsen + Norman 做友好性硬检。Use when user 说"我有一个想法"、"想做一个产品"、"做 MVP"、"写…

app-ui-design

app-ui-design

Mobile app UI design expert for iOS and Android. Use when designing app interfaces, creating design systems, ensuring…

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc11d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about auth-security.

Which coding agents can use auth-security?
auth-security installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.