본문으로 건너뛰기
Previous
Next
Passport.js Complete Guide

Passport.js Complete Guide

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:

  1. Use sessions for web apps, JWT for APIs
  2. Secure session configuration is critical
  3. Support multiple auth strategies easily
  4. Rate limit login attempts
  5. Always use HTTPS in production

Next Steps:

  • Secure with [bcrypt](/en/blog/bcrypt-complete-guide/
  • Add [JWT](/en/blog/jwt-authentication-guide/
  • Build [Express API](/en/blog/express-complete-guide/

Resources:


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Complete Passport.js guide for Node.js authentication. Learn local strategy, OAuth (Google, GitHub, Facebook), JWT, sess… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [Node.js JWT Authentication Complete Guide | bcrypt](/en/blog/nodejs-jwt-authentication-guide/
  • [Node.js Authentication and Security: JWT, bcrypt, Sessions,](/en/blog/nodejs-series-07-auth/
  • [Express.js Complete Guide | Fast Node.js Web Framework](/en/blog/express-complete-guide/

이 글에서 다루는 키워드 (관련 검색어)

Passport, Authentication, Node.js, Express, OAuth, Security 등으로 검색하시면 이 글이 도움이 됩니다.