Skip to content
backend-passport-js logo

Passport.js (Express Authentication)

backend-passport-js

Authentication middleware for Express.js and Node.js applications. Use when building Express APIs that need JWT authentication, OAuth, or custom auth strategies. Provides 500+ authentication strategies. Choose Passport.js over Auth.js for Express backends, pure API servers, or when you need maxim...

SKILL.md

Full skill instructions

Passport.js (Express Authentication)

Overview

Passport.js is the de-facto standard authentication middleware for Express.js. It uses a strategy pattern with 500+ available strategies for different auth mechanisms.

Version: [email protected], [email protected]
Use case: Express.js applications, custom auth flows, pure APIs

Key Benefit: Maximum flexibility, works with any Express app, battle-tested in production.

When to Use This Skill

✅ Use Passport.js when:

  • Building Express.js REST APIs
  • Need JWT-based authentication for APIs
  • Want 500+ auth strategies available
  • Require custom authentication logic
  • Building microservices or pure backend APIs
  • Not using Next.js

❌ Use Auth.js instead when:

  • Building Next.js applications
  • Want minimal configuration
  • Need quick OAuth setup
  • Want serverless/​Edge support

Quick Start

Installation

npm install passport passport-jwt jsonwebtoken
npm install -D @types/​passport @types/​passport-jwt @types/​jsonwebtoken

JWT Strategy Setup

// src/​strategies/​jwt.strategy.ts
import passport from 'passport';
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
import { prisma } from '../​lib/​prisma';

const options = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET!,
};

passport.use(new JwtStrategy(options, async (payload, done) => {
  try {
    const user = await prisma.user.findUnique({ 
      where: { id: payload.sub } 
    });
    
    if (!user) {
      return done(null, false);
    }
    
    return done(null, user);
  } catch (error) {
    return done(error, false);
  }
}));

export default passport;

Express Integration

Complete Setup

// src/​app.ts
import express from 'express';
import passport from 'passport';
import jwt from 'jsonwebtoken';
import { hash, verify } from 'argon2';
import './​strategies/​jwt.strategy';
import { prisma } from './​lib/​prisma';

const app = express();
app.use(express.json());
app.use(passport.initialize());

// Register
app.post('/​auth/​register', async (req, res) => {
  const { email, password, name } = req.body;
  
  const hashedPassword = await hash(password);
  const user = await prisma.user.create({
    data: { email, password: hashedPassword, name },
  });
  
  res.status(201).json({ id: user.id, email: user.email });
});

// Login
app.post('/​auth/​login', async (req, res) => {
  const { email, password } = req.body;
  
  const user = await prisma.user.findUnique({ where: { email } });
  if (!user || !user.password) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  
  const valid = await verify(user.password, password);
  if (!valid) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  
  const accessToken = jwt.sign(
    { sub: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }
  );
  
  const refreshToken = jwt.sign(
    { sub: user.id },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: '7d' }
  );
  
  res.json({ accessToken, refreshToken });
});

// Protected route
app.get('/​api/​profile',
  passport.authenticate('jwt', { session: false }),
  (req, res) => {
    res.json(req.user);
  }
);

// Refresh token
app.post('/​auth/​refresh', async (req, res) => {
  const { refreshToken } = req.body;
  
  try {
    const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as { sub: string };
    const user = await prisma.user.findUnique({ where: { id: payload.sub } });
    
    if (!user) {
      return res.status(401).json({ message: 'Invalid token' });
    }
    
    const accessToken = jwt.sign(
      { sub: user.id, email: user.email, role: user.role },
      process.env.JWT_SECRET!,
      { expiresIn: '15m' }
    );
    
    res.json({ accessToken });
  } catch {
    res.status(401).json({ message: 'Invalid token' });
  }
});

app.listen(3000);

Middleware Helpers

Role-Based Authorization

// src/​middleware/​auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import passport from 'passport';

// Authenticate and attach user
export const authenticate = passport.authenticate('jwt', { session: false });

// Check specific role
export function requireRole(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = req.user as { role: string } | undefined;
    
    if (!user || !roles.includes(user.role)) {
      return res.status(403).json({ message: 'Forbidden' });
    }
    
    next();
  };
}

// Usage:
app.get('/​admin/​users', 
  authenticate, 
  requireRole('admin'), 
  (req, res) => { /​* ... */ }
);

Optional Authentication

export function optionalAuth(req: Request, res: Response, next: NextFunction) {
  passport.authenticate('jwt', { session: false }, (err, user) => {
    if (user) req.user = user;
    next();
  })(req, res, next);
}

// Route works with or without auth
app.get('/​api/​posts', optionalAuth, (req, res) => {
  const userId = (req.user as { id: string })?.id;
  // Show public posts, or personalized if logged in
});

Integration with tRPC (Express)

// src/​server/​context.ts
import { CreateExpressContextOptions } from '@trpc/​server/​adapters/​express';
import { prisma } from '../​lib/​prisma';

export const createContext = ({ req }: CreateExpressContextOptions) => ({
  user: req.user, // Populated by Passport middleware
  prisma,
});

export type Context = ReturnType<typeof createContext>;
// src/​server/​index.ts
import { createExpressMiddleware } from '@trpc/​server/​adapters/​express';
import passport from 'passport';

// Apply Passport to all /​trpc routes
app.use('/​trpc', 
  passport.authenticate('jwt', { session: false, failWithError: false }),
  createExpressMiddleware({
    router: appRouter,
    createContext,
  })
);

JWT Token Structure

// Access token payload
interface AccessTokenPayload {
  sub: string;      // User ID
  email: string;
  role: string;
  iat: number;      // Issued at
  exp: number;      // Expiration
}

// Generate tokens
function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { sub: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }
  );
  
  const refreshToken = jwt.sign(
    { sub: user.id },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: '7d' }
  );
  
  return { accessToken, refreshToken };
}

Type Definitions

// src/​types/​express.d.ts
import { User } from '@prisma/​client';

declare global {
  namespace Express {
    interface User {
      id: string;
      email: string;
      role: string;
    }
  }
}

Environment Variables

JWT_SECRET=your-access-token-secret-min-32-chars
JWT_REFRESH_SECRET=your-refresh-token-secret-min-32-chars

Rules

Do ✅

  • Use session: false for stateless JWT auth
  • Hash passwords with argon2 or bcrypt
  • Use short-lived access tokens (15min)
  • Use refresh tokens for long sessions
  • Store refresh tokens securely (httpOnly cookie)
  • Validate JWT on every request

Avoid ❌

  • Storing sensitive data in JWT payload
  • Using long-lived access tokens
  • Storing tokens in localStorage (XSS risk)
  • Using weak secrets
  • Skipping password hashing

Common Strategies

StrategyPackageUse Case
JWTpassport-jwtAPI authentication
Localpassport-localUsername/​password
OAuth2passport-oauth2Generic OAuth
Googlepassport-google-oauth20Google login
GitHubpassport-github2GitHub login

Auth.js vs Passport.js Decision Tree

ScenarioRecommendation
Next.js App RouterAuth.js
Express.js APIPassport.js
Quick OAuth setupAuth.js
Custom auth logicPassport.js
Serverless/​EdgeAuth.js
Multiple strategiesPassport.js
Pure JWT APIPassport.js

Troubleshooting

"401 on every request":
  → Check Authorization header format: "Bearer <token>"
  → Verify JWT_SECRET matches between sign and verify
  → Check token expiration

"req.user is undefined":
  → Ensure passport.initialize() is called
  → Check authenticate middleware is applied
  → Verify strategy name matches

"CORS issues with auth":
  → Set credentials: true in CORS config
  → Include origin whitelist
  → Allow Authorization header

"Token not refreshing":
  → Check refresh token secret is different
  → Verify refresh token hasn't expired
  → Store refresh token in httpOnly cookie

File Structure

src/
├── strategies/
│   └── jwt.strategy.ts    # JWT strategy config
├── middleware/
│   └── auth.middleware.ts # Auth helpers
├── routes/
│   └── auth.routes.ts     # Auth endpoints
└── types/
    └── express.d.ts       # Type extensions

References

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