JSON Web Tokens for authentication. Covers token structure, signing, and validation. Use for stateless authentication. USE WHEN: user mentions "JWT", "token authentication", "access token", "refresh token", asks about "stateless auth", "token signing", "token validation" DO NOT USE FOR: session-based auth (use session management), OAuth flows (use oauth2 skill), NextAuth.js (use nextauth skill)

GitHub
安装命令
npx skhub add claude-dev-suite/jwt
Markdown
SKILL.md

JWT Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: jwt for comprehensive documentation.

Token Structure

header.payload.signature

Header: { "alg": "HS256", "typ": "JWT" }
Payload: { "sub": "1234", "name": "John", "iat": 1516239022 }
Signature: HMACSHA256(base64(header) + "." + base64(payload), secret)

Node.js Implementation

import jwt from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET!;

// Generate token
function generateToken(user: User): string {
  return jwt.sign(
    { sub: user.id, email: user.email },
    SECRET,
    { expiresIn: '1h' }
  );
}

// Verify token
function verifyToken(token: string): JwtPayload {
  return jwt.verify(token, SECRET) as JwtPayload;
}

// Refresh token pattern
function generateRefreshToken(user: User): string {
  return jwt.sign(
    { sub: user.id, type: 'refresh' },
    SECRET,
    { expiresIn: '7d' }
  );
}

Middleware

const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.split(' ')[1];
  try {
    req.user = verifyToken(token);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

When NOT to Use This Skill

  • Session-based authentication - Use traditional server-side sessions with cookies
  • OAuth 2.0 flows - Use oauth2 skill for third-party authentication
  • NextAuth.js - Use nextauth skill for Next.js authentication
  • Simple internal APIs - API keys might be sufficient

Best Practices

DoDon't
Use HTTPSStore in localStorage (use httpOnly cookies)
Short expiry (15m-1h)Put sensitive data in payload
Validate all claimsUse weak secrets
Use refresh tokensIgnore expiration

Anti-Patterns

Anti-PatternWhy It's BadCorrect Approach
Storing JWT in localStorageVulnerable to XSS attacksUse httpOnly cookies
Long-lived access tokensSecurity risk if compromised15-minute expiry + refresh tokens
Weak secrets (< 32 bytes)Easy to brute forceUse 256-bit random secret
Ignoring algorithm verificationAlgorithm confusion attacksExplicitly specify allowed algorithms
Putting passwords in payloadToken is base64, not encryptedOnly non-sensitive claims
No token revocationCan't logout usersImplement blacklist or token versioning

Quick Troubleshooting

IssueCauseSolution
"Invalid signature"Wrong secret or algorithmVerify JWT_SECRET matches, check algorithm
"Token expired"exp claim in pastImplement refresh token flow
"Missing token"Authorization header not sentCheck Authorization: Bearer <token>
Token not recognizedMalformed tokenVerify header.payload.signature format
CORS errors with cookiesSameSite/Secure flagsSet sameSite:'strict', secure:true
Logout doesn't workTokens are statelessImplement revocation with Redis/DB

Standard Claims

ClaimPurpose
subSubject (user ID)
iatIssued at
expExpiration
issIssuer
audAudience

Production Readiness

Security Configuration

// Use asymmetric keys (RS256) for production
import * as jose from 'jose';

// Generate key pair (run once, store securely)
// openssl genrsa -out private.pem 2048
// openssl rsa -in private.pem -pubout -out public.pem

const privateKey = await jose.importPKCS8(
  process.env.JWT_PRIVATE_KEY!,
  'RS256'
);
const publicKey = await jose.importSPKI(
  process.env.JWT_PUBLIC_KEY!,
  'RS256'
);

// Sign token
async function generateToken(user: User): Promise<string> {
  return new jose.SignJWT({
    sub: user.id,
    email: user.email,
  })
    .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
    .setIssuedAt()
    .setIssuer(process.env.JWT_ISSUER!)
    .setAudience(process.env.JWT_AUDIENCE!)
    .setExpirationTime('15m')  // Short-lived access token
    .sign(privateKey);
}

// Verify token
async function verifyToken(token: string): Promise<jose.JWTPayload> {
  const { payload } = await jose.jwtVerify(token, publicKey, {
    issuer: process.env.JWT_ISSUER!,
    audience: process.env.JWT_AUDIENCE!,
  });
  return payload;
}

Secure Token Storage

// Server-side: HttpOnly cookie for access token
res.cookie('access_token', token, {
  httpOnly: true,     // Prevents XSS access
  secure: true,       // HTTPS only
  sameSite: 'strict', // CSRF protection
  maxAge: 15 * 60 * 1000, // 15 minutes
  path: '/',
});

// Refresh token in separate cookie
res.cookie('refresh_token', refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
  path: '/api/auth/refresh', // Only sent to refresh endpoint
});

Token Rotation & Revocation

// Refresh token rotation
async function refreshTokens(refreshToken: string) {
  // Verify refresh token
  const payload = await verifyRefreshToken(refreshToken);

  // Check if refresh token is in blacklist (revoked)
  if (await isTokenRevoked(refreshToken)) {
    throw new Error('Token revoked');
  }

  // Revoke old refresh token
  await revokeToken(refreshToken);

  // Generate new tokens
  const user = await db.users.findUnique({ where: { id: payload.sub } });
  return {
    accessToken: await generateToken(user),
    refreshToken: await generateRefreshToken(user),
  };
}

// Token revocation with Redis
async function revokeToken(token: string): Promise<void> {
  const payload = await jose.decodeJwt(token);
  const ttl = payload.exp! - Math.floor(Date.now() / 1000);
  if (ttl > 0) {
    await redis.set(`revoked:${token}`, '1', 'EX', ttl);
  }
}

// Logout: revoke all user tokens
async function logoutAll(userId: string): Promise<void> {
  // Increment user's token version, invalidating all existing tokens
  await db.users.update({
    where: { id: userId },
    data: { tokenVersion: { increment: 1 } },
  });
}

Algorithm Security

// NEVER allow 'none' algorithm
// ALWAYS specify allowed algorithms explicitly
const { payload } = await jose.jwtVerify(token, publicKey, {
  algorithms: ['RS256'], // Only allow RS256
  issuer: process.env.JWT_ISSUER!,
  audience: process.env.JWT_AUDIENCE!,
});

// Validate token type to prevent token confusion
if (payload.type !== 'access') {
  throw new Error('Invalid token type');
}

Monitoring Metrics

MetricAlert Threshold
Token verification failures> 100/min
Refresh token reuse attempts> 10/min
Expired token requests> 500/min
Invalid signature errors> 50/min

Claims Validation

async function validateTokenClaims(payload: jose.JWTPayload): Promise<void> {
  // Check required claims
  if (!payload.sub || !payload.iat || !payload.exp) {
    throw new Error('Missing required claims');
  }

  // Check user still exists and is active
  const user = await db.users.findUnique({ where: { id: payload.sub } });
  if (!user || !user.isActive) {
    throw new Error('User not found or inactive');
  }

  // Check token version (for logout-all functionality)
  if (payload.tokenVersion !== user.tokenVersion) {
    throw new Error('Token invalidated');
  }
}

Checklist

  • Use RS256 (asymmetric) in production
  • Short access token expiry (15 minutes)
  • Refresh tokens with rotation
  • HttpOnly cookies (not localStorage)
  • Secure + SameSite cookie flags
  • Token revocation mechanism
  • Validate issuer and audience
  • Specify allowed algorithms explicitly
  • Include token version for logout-all
  • Monitor verification failures
  • Rate limit token endpoints

Reference Documentation

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/authentication/jwt

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1