Claude-skill-registry authentication-strategies
Authentication patterns including JWT, sessions, and OAuth. Use when implementing user authentication.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/authentication-strategies" ~/.claude/skills/majiayu000-claude-skill-registry-authentication-strategies && rm -rf "$T"
manifest:
skills/data/authentication-strategies/SKILL.mdsource content
Authentication Strategies Skill
This skill covers authentication patterns for Node.js APIs.
When to Use
Use this skill when:
- Implementing user authentication
- Setting up JWT token handling
- Integrating OAuth providers
- Managing user sessions
Core Principle
DEFENSE IN DEPTH - Multiple layers of security. Never trust client input. Always validate tokens server-side.
JWT Authentication
Setup
npm install @fastify/jwt bcrypt npm install -D @types/bcrypt
JWT Plugin
// src/plugins/auth.ts import { FastifyPluginAsync, FastifyRequest } from 'fastify'; import fp from 'fastify-plugin'; import jwt from '@fastify/jwt'; declare module '@fastify/jwt' { interface FastifyJWT { payload: { userId: string; role: 'USER' | 'ADMIN'; iat: number; exp: number; }; user: { userId: string; role: 'USER' | 'ADMIN'; }; } } declare module 'fastify' { interface FastifyInstance { authenticate: (request: FastifyRequest) => Promise<void>; authenticateOptional: (request: FastifyRequest) => Promise<void>; } } const authPlugin: FastifyPluginAsync = async (fastify) => { await fastify.register(jwt, { secret: process.env.JWT_SECRET!, sign: { expiresIn: '15m', // Short-lived access tokens }, }); // Required authentication fastify.decorate('authenticate', async (request: FastifyRequest) => { await request.jwtVerify(); }); // Optional authentication fastify.decorate('authenticateOptional', async (request: FastifyRequest) => { try { await request.jwtVerify(); } catch { // Continue without user } }); }; export default fp(authPlugin, { name: 'auth' });
Auth Routes
// src/routes/auth.ts import { FastifyPluginAsync } from 'fastify'; import { z } from 'zod'; import bcrypt from 'bcrypt'; const RegisterSchema = z.object({ email: z.string().email(), password: z.string().min(8), name: z.string().min(1), }); const LoginSchema = z.object({ email: z.string().email(), password: z.string(), }); const authRoutes: FastifyPluginAsync = async (fastify) => { // Register fastify.post<{ Body: z.infer<typeof RegisterSchema> }>('/register', async (request, reply) => { const { email, password, name } = request.body; // Check if user exists const existing = await fastify.db.user.findUnique({ where: { email } }); if (existing) { return reply.status(400).send({ error: 'Email already registered' }); } // Hash password const hashedPassword = await bcrypt.hash(password, 12); // Create user const user = await fastify.db.user.create({ data: { email, password: hashedPassword, name }, }); // Generate tokens const accessToken = fastify.jwt.sign({ userId: user.id, role: user.role, }); const refreshToken = await createRefreshToken(fastify, user.id); return reply.status(201).send({ user: { id: user.id, email: user.email, name: user.name }, accessToken, refreshToken, }); }); // Login fastify.post<{ Body: z.infer<typeof LoginSchema> }>('/login', async (request, reply) => { const { email, password } = request.body; const user = await fastify.db.user.findUnique({ where: { email } }); if (!user) { return reply.status(401).send({ error: 'Invalid credentials' }); } const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) { return reply.status(401).send({ error: 'Invalid credentials' }); } const accessToken = fastify.jwt.sign({ userId: user.id, role: user.role, }); const refreshToken = await createRefreshToken(fastify, user.id); return { user: { id: user.id, email: user.email, name: user.name }, accessToken, refreshToken, }; }); // Refresh token fastify.post<{ Body: { refreshToken: string } }>('/refresh', async (request, reply) => { const { refreshToken } = request.body; const token = await fastify.db.refreshToken.findUnique({ where: { token: refreshToken }, include: { user: true }, }); if (!token || token.expiresAt < new Date()) { return reply.status(401).send({ error: 'Invalid refresh token' }); } // Rotate refresh token await fastify.db.refreshToken.delete({ where: { id: token.id } }); const newRefreshToken = await createRefreshToken(fastify, token.userId); const accessToken = fastify.jwt.sign({ userId: token.user.id, role: token.user.role, }); return { accessToken, refreshToken: newRefreshToken }; }); // Logout fastify.post('/logout', { preHandler: [fastify.authenticate], }, async (request, reply) => { // Delete all refresh tokens for user await fastify.db.refreshToken.deleteMany({ where: { userId: request.user.userId }, }); return { success: true }; }); }; async function createRefreshToken(fastify: FastifyInstance, userId: string): Promise<string> { const token = crypto.randomUUID(); const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days await fastify.db.refreshToken.create({ data: { token, userId, expiresAt }, }); return token; } export default authRoutes;
Protected Routes
// src/routes/users.ts import { FastifyPluginAsync } from 'fastify'; const usersRoutes: FastifyPluginAsync = async (fastify) => { // Protected route fastify.get('/me', { preHandler: [fastify.authenticate], }, async (request) => { const user = await fastify.db.user.findUnique({ where: { id: request.user.userId }, select: { id: true, email: true, name: true, role: true }, }); return user; }); // Admin only route fastify.get('/admin/users', { preHandler: [fastify.authenticate], }, async (request, reply) => { if (request.user.role !== 'ADMIN') { return reply.status(403).send({ error: 'Forbidden' }); } return fastify.db.user.findMany(); }); }; export default usersRoutes;
OAuth 2.0 Integration
Google OAuth
// src/routes/oauth.ts import { FastifyPluginAsync } from 'fastify'; const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID!; const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET!; const GOOGLE_REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI!; const oauthRoutes: FastifyPluginAsync = async (fastify) => { // Initiate Google OAuth fastify.get('/google', async (request, reply) => { const params = new URLSearchParams({ client_id: GOOGLE_CLIENT_ID, redirect_uri: GOOGLE_REDIRECT_URI, response_type: 'code', scope: 'email profile', access_type: 'offline', }); return reply.redirect( `https://accounts.google.com/o/oauth2/v2/auth?${params}` ); }); // Google OAuth callback fastify.get<{ Querystring: { code: string } }>('/google/callback', async (request, reply) => { const { code } = request.query; // Exchange code for tokens const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ code, client_id: GOOGLE_CLIENT_ID, client_secret: GOOGLE_CLIENT_SECRET, redirect_uri: GOOGLE_REDIRECT_URI, grant_type: 'authorization_code', }), }); const tokens = await tokenResponse.json(); // Get user info const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { headers: { Authorization: `Bearer ${tokens.access_token}` }, }); const googleUser = await userResponse.json(); // Find or create user let user = await fastify.db.user.findUnique({ where: { email: googleUser.email }, }); if (!user) { user = await fastify.db.user.create({ data: { email: googleUser.email, name: googleUser.name, googleId: googleUser.id, password: '', // No password for OAuth users }, }); } // Generate JWT const accessToken = fastify.jwt.sign({ userId: user.id, role: user.role, }); // Redirect to frontend with token return reply.redirect(`${process.env.FRONTEND_URL}/auth/callback?token=${accessToken}`); }); }; export default oauthRoutes;
API Key Authentication
// src/plugins/api-key.ts import { FastifyPluginAsync, FastifyRequest } from 'fastify'; import fp from 'fastify-plugin'; declare module 'fastify' { interface FastifyRequest { apiKey?: { id: string; name: string; permissions: string[] }; } interface FastifyInstance { authenticateApiKey: (request: FastifyRequest) => Promise<void>; } } const apiKeyPlugin: FastifyPluginAsync = async (fastify) => { fastify.decorate('authenticateApiKey', async (request: FastifyRequest) => { const key = request.headers['x-api-key'] as string | undefined; if (!key) { throw fastify.httpErrors.unauthorized('API key required'); } const apiKey = await fastify.db.apiKey.findUnique({ where: { key }, }); if (!apiKey || !apiKey.active) { throw fastify.httpErrors.unauthorized('Invalid API key'); } // Update last used await fastify.db.apiKey.update({ where: { id: apiKey.id }, data: { lastUsedAt: new Date() }, }); request.apiKey = { id: apiKey.id, name: apiKey.name, permissions: apiKey.permissions, }; }); }; export default fp(apiKeyPlugin);
Password Security
// src/lib/password.ts import bcrypt from 'bcrypt'; import { z } from 'zod'; const SALT_ROUNDS = 12; // Password requirements export const PasswordSchema = z.string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain uppercase letter') .regex(/[a-z]/, 'Password must contain lowercase letter') .regex(/[0-9]/, 'Password must contain number') .regex(/[^A-Za-z0-9]/, 'Password must contain special character'); export async function hashPassword(password: string): Promise<string> { return bcrypt.hash(password, SALT_ROUNDS); } export async function verifyPassword(password: string, hash: string): Promise<boolean> { return bcrypt.compare(password, hash); } export function isPasswordCompromised(password: string): boolean { // Check against common passwords list const commonPasswords = ['password', '123456', 'qwerty']; return commonPasswords.includes(password.toLowerCase()); }
Best Practices
- Short-lived access tokens - 15 minutes maximum
- Rotate refresh tokens - On each use
- Hash passwords - bcrypt with high cost factor
- Rate limit auth endpoints - Prevent brute force
- Secure cookies - HttpOnly, Secure, SameSite
- Validate all tokens - Server-side verification
Notes
- Never store plain-text passwords
- Use HTTPS in production
- Implement account lockout
- Log authentication events
- Support MFA for sensitive operations