Use Cases

How to Build an Authentication System with Claude Code (JWT and OAuth)

How to build an authentication system with Claude Code (JWT and OAuth). A practical guide with code examples.

Why Use Claude Code for Building Authentication Systems

Authentication is directly tied to security, and implementation mistakes can lead to serious vulnerabilities. Claude Code can generate code that follows security best practices, and will suggest mitigations for attack vectors that are easy to overlook.

Implementing JWT Authentication

> Implement JWT authentication in Express + TypeScript.
> Use a two-token scheme with a 15-minute access token and a 7-day refresh token.
> Manage the refresh token in an httpOnly cookie.

Authentication Service

import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { prisma } from './db';

const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET!;
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET!;

interface TokenPayload {
  userId: string;
  email: string;
}

export class AuthService {
  async register(email: string, password: string, name: string) {
    const existing = await prisma.user.findUnique({ where: { email } });
    if (existing) throw new Error('Email already registered');

    const hashedPassword = await bcrypt.hash(password, 12);
    const user = await prisma.user.create({
      data: { email, password: hashedPassword, name },
    });

    return this.generateTokens({ userId: user.id, email: user.email });
  }

  async login(email: string, password: string) {
    const user = await prisma.user.findUnique({ where: { email } });
    if (!user) throw new Error('Invalid credentials');

    const valid = await bcrypt.compare(password, user.password);
    if (!valid) throw new Error('Invalid credentials');

    return this.generateTokens({ userId: user.id, email: user.email });
  }

  async refreshToken(token: string) {
    const payload = jwt.verify(token, REFRESH_TOKEN_SECRET) as TokenPayload;

    // Verify the refresh token's validity in the DB
    const stored = await prisma.refreshToken.findFirst({
      where: { token, userId: payload.userId, revoked: false },
    });
    if (!stored) throw new Error('Invalid refresh token');

    // Revoke the old token (token rotation)
    await prisma.refreshToken.update({
      where: { id: stored.id },
      data: { revoked: true },
    });

    return this.generateTokens(payload);
  }

  private async generateTokens(payload: TokenPayload) {
    const accessToken = jwt.sign(payload, ACCESS_TOKEN_SECRET, {
      expiresIn: '15m',
    });
    const refreshToken = jwt.sign(payload, REFRESH_TOKEN_SECRET, {
      expiresIn: '7d',
    });

    await prisma.refreshToken.create({
      data: { token: refreshToken, userId: payload.userId },
    });

    return { accessToken, refreshToken };
  }
}

Authentication Middleware

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export function authMiddleware(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = header.slice(7);

  try {
    const payload = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!);
    req.user = payload as TokenPayload;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

Router Configuration

import { Router } from 'express';
import { AuthService } from './auth-service';

const router = Router();
const auth = new AuthService();

router.post('/register', async (req, res) => {
  try {
    const { accessToken, refreshToken } = await auth.register(
      req.body.email, req.body.password, req.body.name
    );
    res.cookie('refreshToken', refreshToken, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000,
    });
    res.json({ accessToken });
  } catch (err) {
    res.status(400).json({ error: (err as Error).message });
  }
});

router.post('/login', async (req, res) => {
  try {
    const { accessToken, refreshToken } = await auth.login(
      req.body.email, req.body.password
    );
    res.cookie('refreshToken', refreshToken, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000,
    });
    res.json({ accessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

router.post('/refresh', async (req, res) => {
  try {
    const token = req.cookies.refreshToken;
    const { accessToken, refreshToken } = await auth.refreshToken(token);
    res.cookie('refreshToken', refreshToken, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000,
    });
    res.json({ accessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

export default router;

OAuth Integration (Google)

> Add Google login using Passport.js. Integrate it with the existing JWT authentication.
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';

passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  callbackURL: '/auth/google/callback',
}, async (accessToken, refreshToken, profile, done) => {
  let user = await prisma.user.findFirst({
    where: { providerId: profile.id, provider: 'google' },
  });

  if (!user) {
    user = await prisma.user.create({
      data: {
        email: profile.emails![0].value,
        name: profile.displayName,
        provider: 'google',
        providerId: profile.id,
      },
    });
  }

  done(null, user);
}));

Security Checklist

You can ask Claude Code for a security audit with a prompt like:

> Review the authentication code from a security perspective.
> Check against the OWASP Top 10.

The main items to check include:

  • Are passwords hashed with bcrypt?
  • Is the JWT secret key long enough?
  • Is refresh token rotation implemented?
  • Are CSRF protections in place?
  • Are rate limits configured?

For maintaining code quality including security, automating refactoring is also effective. In addition, writing your authentication policy into CLAUDE.md helps Claude Code generate consistent code.

Summary

With Claude Code, you can efficiently build a robust authentication system that includes JWT auth and OAuth integrations. Because it generates code that follows security best practices, it makes it easier to address vulnerabilities that are easy to miss. In production, always ensure safe secret-key management and enforce HTTPS.

For details, see the official Anthropic documentation.

#Claude Code #authentication #JWT #OAuth #security

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.