Passport.js Complete Guide
이 글의 핵심
Passport.js is authentication middleware for Node.js. It supports 500+ authentication strategies including local, OAuth, and OpenID, making it the standard for authentication.
Introduction
Passport.js is authentication middleware for Node.js. It’s extremely flexible and modular, supporting authentication via username/password, OAuth (Google, Facebook, Twitter), and 500+ other strategies.
Why Passport?
Manual authentication (complex):
// Sessions, cookies, OAuth flows, password hashing...
// Hundreds of lines of code per provider
With Passport:
passport.use(new LocalStrategy(verify));
app.post('/login', passport.authenticate('local'));
1. Installation
npm install passport passport-local express-session
2. Local Strategy (Username/Password)
Setup
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const bcrypt = require('bcrypt');
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Session configuration
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, // 24 hours
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
}
}));
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Configure Local Strategy
passport.use(new LocalStrategy(
async (username, password, done) => {
try {
// Find user
const user = await db.users.findOne({ username });
if (!user) {
return done(null, false, { message: 'Incorrect username' });
}
// Verify password
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return done(null, false, { message: 'Incorrect password' });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// Serialize user for session
passport.serializeUser((user, done) => {
done(null, user.id);
});
// Deserialize user from session
passport.deserializeUser(async (id, done) => {
try {
const user = await db.users.findById(id);
done(null, user);
} catch (error) {
done(error);
}
});
Login Route
app.post('/login', passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true,
}));
// Or with callback
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({ error: info.message });
}
req.logIn(user, (err) => {
if (err) {
return next(err);
}
return res.json({
message: 'Login successful',
user: { id: user.id, username: user.username }
});
});
})(req, res, next);
});
Registration
app.post('/register', async (req, res) => {
try {
const { username, email, password } = req.body;
// Check if user exists
const existing = await db.users.findOne({ username });
if (existing) {
return res.status(409).json({ error: 'Username already exists' });
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 10);
// Create user
const user = await db.users.create({
username,
email,
password: hashedPassword,
});
// Auto-login after registration
req.login(user, (err) => {
if (err) {
return res.status(500).json({ error: 'Login failed' });
}
res.status(201).json({
message: 'Registration successful',
user: { id: user.id, username: user.username }
});
});
} catch (error) {
res.status(500).json({ error: 'Registration failed' });
}
});
Logout
app.post('/logout', (req, res) => {
req.logout((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.json({ message: 'Logged out successfully' });
});
});
3. Protected Routes
// Middleware to check authentication
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ error: 'Not authenticated' });
}
// Protected route
app.get('/dashboard', ensureAuthenticated, (req, res) => {
res.json({
message: 'Welcome to dashboard',
user: req.user,
});
});
// Optional authentication
app.get('/profile/:id', (req, res) => {
if (req.isAuthenticated()) {
// Show full profile
res.json({ profile: 'full', user: req.user });
} else {
// Show public profile
res.json({ profile: 'public' });
}
});
4. Google OAuth
npm install passport-google-oauth20
const GoogleStrategy = require('passport-google-oauth20').Strategy;
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) => {
try {
// Find or create user
let user = await db.users.findOne({ googleId: profile.id });
if (!user) {
user = await db.users.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value,
});
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// Routes
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/dashboard');
}
);
5. GitHub OAuth
npm install passport-github2
const GitHubStrategy = require('passport-github2').Strategy;
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: '/auth/github/callback',
},
async (accessToken, refreshToken, profile, done) => {
try {
let user = await db.users.findOne({ githubId: profile.id });
if (!user) {
user = await db.users.create({
githubId: profile.id,
username: profile.username,
name: profile.displayName,
avatar: profile.photos[0].value,
});
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// Routes
app.get('/auth/github',
passport.authenticate('github', { scope: ['user:email'] })
);
app.get('/auth/github/callback',
passport.authenticate('github', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/dashboard');
}
);
6. JWT Strategy
npm install passport-jwt jsonwebtoken
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const jwt = require('jsonwebtoken');
// Configure JWT Strategy
passport.use(new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
},
async (payload, done) => {
try {
const user = await db.users.findById(payload.sub);
if (!user) {
return done(null, false);
}
return done(null, user);
} catch (error) {
return done(error, false);
}
}
));
// Login route (generate JWT)
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
const user = await db.users.findOne({ username });
if (!user || !await bcrypt.compare(password, user.password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate JWT
const token = jwt.sign(
{ sub: user.id, username: user.username },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token, user: { id: user.id, username: user.username } });
});
// Protected route
app.get('/api/profile',
passport.authenticate('jwt', { session: false }),
(req, res) => {
res.json({ user: req.user });
}
);
7. Multiple Strategies
// Local + Google + GitHub
passport.use('local', new LocalStrategy(/* ... */));
passport.use('google', new GoogleStrategy(/* ... */));
passport.use('github', new GitHubStrategy(/* ... */));
// Login with local
app.post('/login', passport.authenticate('local'));
// Login with Google
app.get('/auth/google', passport.authenticate('google'));
// Login with GitHub
app.get('/auth/github', passport.authenticate('github'));
8. Custom Callback
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
return res.status(500).json({ error: 'Internal error' });
}
if (!user) {
return res.status(401).json({ error: info.message || 'Login failed' });
}
req.logIn(user, (err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
// Custom response
return res.json({
success: true,
user: {
id: user.id,
username: user.username,
email: user.email,
},
token: generateToken(user), // If using JWT
});
});
})(req, res, next);
});
9. Session Store (Production)
With Redis
npm install connect-redis redis
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
// Create Redis client
const redisClient = createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
});
redisClient.connect();
// Configure session with Redis
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
}
}));
10. Remember Me
// Login with remember me
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err || !user) {
return res.status(401).json({ error: 'Login failed' });
}
req.logIn(user, (err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
// Set longer expiry for remember me
if (req.body.rememberMe) {
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
}
res.json({ success: true, user });
});
})(req, res, next);
});
11. Real-World Example
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const bcrypt = require('bcrypt');
const app = express();
// Middleware
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
}));
app.use(passport.initialize());
app.use(passport.session());
// Local Strategy
passport.use(new LocalStrategy(
{ usernameField: 'email' },
async (email, password, done) => {
try {
const user = await db.users.findOne({ email });
if (!user) {
return done(null, false, { message: 'User not found' });
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
return done(null, false, { message: 'Invalid password' });
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async (id, done) => {
try {
const user = await db.users.findById(id);
done(null, user);
} catch (error) {
done(error);
}
});
// Routes
app.post('/api/register', async (req, res) => {
const { email, password, name } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = await db.users.create({
email,
password: hashedPassword,
name,
});
req.login(user, (err) => {
if (err) return res.status(500).json({ error: err.message });
res.json({ user: { id: user.id, email: user.email, name: user.name } });
});
});
app.post('/api/login', passport.authenticate('local'), (req, res) => {
res.json({ user: req.user });
});
app.post('/api/logout', (req, res) => {
req.logout(() => {
res.json({ message: 'Logged out' });
});
});
app.get('/api/me', (req, res) => {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({ user: req.user });
});
app.listen(3000);
12. Best Practices
1. Secure Session Configuration
app.use(session({
secret: process.env.SESSION_SECRET, // Use env var
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000,
httpOnly: true, // Prevent XSS
secure: true, // HTTPS only in production
sameSite: 'strict', // CSRF protection
}
}));
2. Rate Limit Login Attempts
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts',
});
app.post('/login', loginLimiter, passport.authenticate('local'));
3. Use HTTPS in Production
if (process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect(`https://${req.headers.host}${req.url}`);
}
next();
});
}
Summary
Passport.js provides flexible authentication:
- 500+ strategies - local, OAuth, OpenID
- Session management built-in
- Easy to extend with custom strategies
- Works with JWT for APIs
- Production-ready and battle-tested
Key Takeaways:
- Use sessions for web apps, JWT for APIs
- Secure session configuration is critical
- Support multiple auth strategies easily
- Rate limit login attempts
- Always use HTTPS in production
Next Steps:
- Secure with bcrypt
- Add JWT
- Build Express API
Resources:
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Complete Passport.js guide for Node.js authentication. Learn local strategy, OAuth (Google, GitHub, Facebook), JWT, sess…
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Node.js JWT Authentication Complete Guide | bcrypt
- Node.js Authentication and Security: JWT, bcrypt, Sessions,
- Express.js Complete Guide | Fast Node.js Web Framework
Keywords Covered in This Article (Related Search Terms)
This article covers Passport, Authentication, Node.js, Express, OAuth, Security.