| 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
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
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());
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 });
});
app.post('/auth/login', async (req, res) => {
{ email, password } = req.;
user = prisma..({ : { email } });
(!user || !user.) {
res.().({ : });
}
valid = (user., password);
(!valid) {
res.().({ : });
}
accessToken = jwt.(
{ : user., : user., : user. },
process..!,
{ : }
);
refreshToken = jwt.(
{ : user. },
process..!,
{ : }
);
res.({ accessToken, refreshToken });
});
app.(,
passport.(, { : }),
{
res.(req.);
}
);
app.(, (req, res) => {
{ refreshToken } = req.;
{
payload = jwt.(refreshToken, process..!) { : };
user = prisma..({ : { : payload. } });
(!user) {
res.().({ : });
}
accessToken = jwt.(
{ : user., : user., : user. },
process..!,
{ : }
);
res.({ accessToken });
} {
res.().({ : });
}
});
app.();
Middleware Helpers
Role-Based Authorization
import { Request, Response, NextFunction } from 'express';
import passport from 'passport';
export const authenticate = passport.authenticate('jwt', { session: false });
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();
};
}
app.get('/admin/users',
authenticate,
(),
{ }
);
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);
}
app.get('/api/posts', optionalAuth, (req, res) => {
const userId = (req.user as { id: string })?.id;
});
Integration with tRPC (Express)
import { CreateExpressContextOptions } from '@trpc/server/adapters/express';
import { prisma } from '../lib/prisma';
export const createContext = ({ req }: CreateExpressContextOptions) => ({
user: req.user,
prisma,
});
export type Context = ReturnType<typeof createContext>;
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import passport from 'passport';
app.use('/trpc',
passport.authenticate('jwt', { session: false, failWithError: false }),
createExpressMiddleware({
router: appRouter,
createContext,
})
);
JWT Token Structure
interface AccessTokenPayload {
sub: string;
email: string;
role: string;
iat: number;
exp: number;
}
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
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
| Strategy | Package | Use Case |
|---|
| JWT | passport-jwt | API authentication |
| Local | passport-local | Username/password |
| OAuth2 | passport-oauth2 | Generic OAuth |
| Google | 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
→
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