BioPlayground

🧬
목록으로

Passport.js — 인증 미들웨어

Passport.js의 구조와 Local Strategy를 이해하고, Express에서 로그인 인증을 구현합니다.

중급
|
12
|
검증 완료 (2026-07)
Passport.js인증 미들웨어Local Strategy직렬화세션 인증
진행률0/23 (0%)

Passport.js — 인증 미들웨어

이 토픽을 마치면

Passport.js가 왜 필요한지 이해하고, Local Strategy로 아이디/비밀번호 인증을 구현할 수 있으며, serialize/deserialize의 역할을 설명할 수 있습니다.


왜 Passport.js를 쓰는가

이전 토픽에서 세션 기반 인증을 직접 구현했습니다. 작동은 하지만, 인증 방식이 다양해지면 코드가 복잡해집니다.

  • 아이디/비밀번호 로그인
  • Google 로그인
  • GitHub 로그인
  • JWT 토큰 인증

각각을 직접 구현하면 라우트마다 인증 로직이 흩어집니다. Passport.js는 이 문제를 해결하는 인증 전문 미들웨어입니다. 500+ 가지의 인증 전략(Strategy)을 플러그인 형태로 제공합니다.


핵심 개념 — Strategy 패턴

Passport.js는 Strategy 패턴을 사용합니다. "어떻게 인증할 것인가"를 Strategy 객체로 분리합니다.

text
Passport.js (프레임워크)
├── Local Strategy    → 아이디/비밀번호
├── Google Strategy   → Google OAuth
├── GitHub Strategy   → GitHub OAuth
└── JWT Strategy      → JSON Web Token

각 Strategy는 독립적인 npm 패키지입니다. 필요한 것만 설치해서 씁니다.


설치

bash
npm install passport passport-local express-session
  • passport: 코어 라이브러리
  • passport-local: 아이디/비밀번호 인증 전략
  • express-session: 세션 관리 (Passport가 내부적으로 사용)

Local Strategy 설정

javascript
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const app = express();

// 가상의 사용자 DB
const users = [
  { id: 1, username: 'alice', password: 'pass123', displayName: 'Alice Kim' },
  { id: 2, username: 'bob', password: 'pass456', displayName: 'Bob Lee' }
];

// 1. Strategy 등록
passport.use(new LocalStrategy(
  (username, password, done) => {
    const user = users.find(u => u.username === username);
    if (!user) {
      return done(null, false, { message: 'User not found' });
    }
    if (user.password !== password) {
      return done(null, false, { message: 'Wrong password' });
    }
    return done(null, user);
  }
));

done 콜백의 세 가지 호출 패턴:

호출의미
done(null, user)인증 성공. user 객체 전달
done(null, false, {message})인증 실패. 사용자 없음 또는 비밀번호 불일치
done(err)시스템 에러. DB 연결 실패 등

Serialize / Deserialize

세션에 사용자 전체 객체를 저장하면 메모리 낭비입니다. Passport는 세션에 **최소한의 식별 정보(보통 ID)**만 저장하고, 요청마다 그 ID로 사용자를 복원합니다.

javascript
// 2. 세션에 저장할 데이터 (로그인 시 1회)
passport.serializeUser((user, done) => {
  done(null, user.id);  // 세션에 user.id만 저장
});

// 3. 세션에서 사용자 복원 (매 요청마다)
passport.deserializeUser((id, done) => {
  const user = users.find(u => u.id === id);
  done(null, user);     // req.user에 전체 사용자 객체 설정
});
text
로그인 시:   user 객체 → serializeUser → 세션에 id=1 저장
매 요청 시:  세션에서 id=1 → deserializeUser → user 객체 복원 → req.user

deserializeUser에서 DB를 조회하면, 세션에 ID만 저장하면서도 매 요청에서 최신 사용자 정보를 사용할 수 있습니다.


미들웨어 연결

javascript
// 4. 미들웨어 설정 (순서 중요!)
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session({
  secret: process.env.SESSION_SECRET || 'dev-secret',
  resave: false,
  saveUninitialized: false
}));
app.use(passport.initialize());   // Passport 초기화
app.use(passport.session());      // 세션과 연결

순서가 중요합니다: express-sessionpassport.initialize()passport.session(). 순서가 바뀌면 세션이 제대로 작동하지 않습니다.


로그인 / 로그아웃 라우트

javascript
// 로그인
app.post('/login',
  passport.authenticate('local', {
    successRedirect: '/dashboard',
    failureRedirect: '/login',
    failureMessage: true
  })
);

// 또는 JSON 응답을 원할 때
app.post('/api/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);
      res.json({ message: `Welcome, ${user.displayName}` });
    });
  })(req, res, next);
});

// 로그아웃
app.post('/logout', (req, res) => {
  req.logout((err) => {
    if (err) return res.status(500).json({ error: 'Logout failed' });
    res.json({ message: 'Logged out' });
  });
});

passport.authenticate('local')은 앞서 등록한 LocalStrategy를 실행합니다. 성공하면 serializeUser가 호출되고, 세션에 사용자 ID가 저장됩니다.


보호된 라우트

javascript
function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  res.status(401).json({ error: 'Login required' });
}

app.get('/dashboard', ensureAuthenticated, (req, res) => {
  res.json({
    message: `Hello, ${req.user.displayName}`,
    user: { id: req.user.id, username: req.user.username }
  });
});

req.isAuthenticated()는 Passport가 제공하는 메서드로, 세션에 유효한 사용자가 있으면 true를 반환합니다. req.userdeserializeUser에서 복원된 사용자 객체입니다.


전체 흐름 정리

text
1. 서버 시작
   passport.use(LocalStrategy)    — 인증 방법 등록
   passport.serializeUser()       — 세션 저장 방법 등록
   passport.deserializeUser()     — 세션 복원 방법 등록

2. POST /login
   passport.authenticate('local') — Strategy 실행
   → 성공: serializeUser → 세션에 ID 저장 → 응답
   → 실패: 401 응답

3. GET /dashboard (인증 필요)
   세션 쿠키 확인 → deserializeUser → req.user 설정
   ensureAuthenticated → req.isAuthenticated() → true → 라우트 실행

4. POST /logout
   req.logout() → 세션에서 사용자 제거 → 응답

자주 하는 실수

실수결과해결
app.use(session(...)) 전에 passport.session() 호출세션이 초기화 안 됨express-session → passport.initialize() → passport.session() 순서
serializeUser에서 전체 user 객체 저장세션 크기 폭발user.id만 저장
deserializeUser에서 비동기 에러 무시인증 실패 시 서버 크래시done(err) 호출
Strategy callback에서 done() 호출 누락요청이 무한 대기모든 분기에서 done() 호출 확인

핵심 정리

개념정리
Passport.js인증 전문 미들웨어. Strategy 패턴으로 다양한 인증 방식 지원
Strategy인증 방법을 캡슐화한 객체 (Local, Google, JWT 등)
serializeUser세션에 저장할 최소 정보 결정 (보통 user.id)
deserializeUser매 요청마다 세션의 ID로 사용자 객체 복원
req.userdeserializeUser가 복원한 현재 로그인 사용자
req.isAuthenticated()로그인 상태 확인

Passport.js의 가치는 "인증 로직의 표준화"입니다. Local에서 Google OAuth로 바꿀 때, Strategy만 교체하면 됩니다. serialize/deserialize, 미들웨어 체인, req.user — 나머지 코드는 그대로 유지됩니다.