Skip to content
authentication-strategies logo

Authentication Strategies Skill

authentication-strategies

Authentication patterns including JWT, sessions, and OAuth. Use when implementing user authentication.

SKILL.md

Full skill instructions

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

  1. Short-lived access tokens - 15 minutes maximum
  2. Rotate refresh tokens - On each use
  3. Hash passwords - bcrypt with high cost factor
  4. Rate limit auth endpoints - Prevent brute force
  5. Secure cookies - HttpOnly, Secure, SameSite
  6. 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

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View