| name | backend-passport-js |
| description | 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 maximum control over auth flow. |
| allowed-tools | Read, Edit, Write, Bash (*) |
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: passport@0.7.x, passport-jwt@4.x
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: falsefor 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
| Strategy | Package | Use Case |
|---|---|---|
| JWT | passport-jwt |
API authentication |
| Local | passport-local |
Username/password |
| OAuth2 | passport-oauth2 |
Generic OAuth |
passport-google-oauth20 |
Google login | |
| GitHub | passport-github2 |
GitHub login |
Auth.js vs Passport.js Decision Tree
| Scenario | Recommendation |
|---|---|
| Next.js App Router | Auth.js |
| Express.js API | Passport.js |
| Quick OAuth setup | Auth.js |
| Custom auth logic | Passport.js |
| Serverless/Edge | Auth.js |
| Multiple strategies | Passport.js |
| Pure JWT API | Passport.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
- https://passportjs.org — Official documentation
- https://www.npmjs.com/package/passport-jwt — JWT strategy
- http://www.passportjs.org/packages/ — All strategies