[passport] 1. passport-local

Passport란 무엇인가?

Simple, unobtrusive authentication for Node.js

즉, node로 개발된 서버를 위한 인증 라이브러리이다.

Passport는 다양한 인증 방식을 전략(Strategy)이라는 이름으로 제공한다.
Passport는 말그대로 복잡한 인증 과정에서 복잡한 부분은 라이브러리에게 맡기고 개발자는
핵심 로직에 집중할 수 있도록 하기 위해 사용한다.

그러나 개발자는 이 복잡한 부분이 어떻게 수행되는지 정도는 알고 있어야 한다.

이 포스팅에서는 서버에서 직접 아이디와 비밀번호를 받아 처리하는 passport-local 방식을 이용해본다.

passport-local

passport-local은 사용자가 직접 입력한 아이디와 비밀번호로 인증을 수행한다.

passport-local은 session과 함께 사용되며, 인증을 수행한 후 세션 db에 사용자 정보를 저장하고 불러오는 동작을 간소화하여 제공한다.

npm install passport passport-local

passport를 사용하는 법

1. 전략(Strategy) 세우기

간단하게 passport-local을 이용한 아이디/비밀번호 인증 전략,

passport-jwt를 이용한 토큰 유효성 검증 전략을 세워보았다.

const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');

// 기본적으로 username과 password를 받으나 별도의 이름을 지정해줄 수 있다.
const localConfig = {
  usernameField: 'name',
  passwordField: 'pwd',
};

passport.use(new LocalStrategy(localConfig, (name, pwd, done) => {
    // 실패 시
    if (name !== 'yh' || pwd !== '1234') {
      return done(null, false, { message: '잘못된 유저 정보!' });
    }
    // 성공 시
    const user = { name, pwd };
    return done(null, user);
}));

이 예시 코드에서는 반드시 아이디가 yh, 비밀번호가 1234라고 가정한다.
즉, request body로 { name: 'yh', pwd: '1234' }가 들어와야만 인증을 통과할 수 있는 것이다.

인증에 실패하는 경우 세 번째 인자로 실패 이유를 담은 객체를 넘겨주면 passport가 처리해 준다.

인증에 성공한 경우 두 번째 인자로 사용자 정보를 넘겨야 된다.
이후에 이 정보가 req.user 그 자체로 설정된다.

2. API에서 전략 이용하기

기본

가장 기본적인 예시이다.

app.post('/login',
  // 전략이 실패하면 401 Unauthorized를 반환하며 다음 콜백이 실행되지 않는다.
  passport.authenticate('local'),
  (req, res) => {
    // 전략이 성공하면 실행될 콜백 함수
    // req.user에는 done(null, user)의 두 번째 인자가 담겨 있다.
    res.send(`환영합니다, ${req.user.name} 님!`);
  }
);

이외에도 successRedirect, failureRedirect를 이용하여 성공 또는 실패 시 리다이렉팅을 수행하거나
successFlash, failureFlash를 이용하여 플래시 메시지를 전달할 수 있다.

자세한 내용은 공식 문서를 참조.

직접 예외처리

나는 진행중인 프로젝트에서 인증 실패 시 프론트 단에서 메시지를 띄워줘야 하는 상황
이었기 때문에 이 방법을 택했다.

성공의 경우에도, 실패의 경우에도 수행할 동작을 직접 구현하는 방법이다.

이 경우 serializeUser에 user 정보를 넘겨주기 위해 req.login 함수를 이용한다.

serializeUser, deserializeUser에 대해서는 이후에 설명한다.

app.post('/login', (req, res) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) return console.error(err);
    if (!user) return res.status(400).json({ message: 'failed login' });
    const token = jwt.sign(user.name, 'yh');
    req.login(user, (err) => {
            if (err) return colsole.error(err);
            return res.json({ token });
        });
  })(req, res);
});

3. 미들웨어로 등록하기

API가 호출될 때마다 app 단에서 passport를 거쳐야 한다.

그래야 API에서 req.user라고 명시해 준 부분이 제대로 작동하기 때문이다.

이 코드는 app.js에서 미들웨어를 등록하는 부분에 함께 등록해주면 된다.

단, passport-local은 내부적으로 세션을 사용하기 때문에 해당 코드는
세션 등록 코드 이후에 위치해야 한다.

app.use(passport.initialize());

4. serializeUser, deserializeUser 등록하기

로그인을 위한 전략이 성공하면 passport는 자동으로 serializaeUser 메소드에 등록된 콜백을
호출한다.

serializeUser는 done을 호출하며, 여기서 두 번째 인자로 전달된 값이 세션에 저장된다.

또한 이 인자는 deserializeUser의 콜백 함수의 두 번째 인자로 전달되며,
deserializeUser는 API call이 발생할 때마다 호출된다.

passport.serializeUser((user, done) => {
  return done(null, user);
});
passport.deserializeUser((user: User, done) => {
  return done(null, user);
});

이외에도 제3의 서비스로부터 사용자의 정보를 받아오는 passport-facebook, passport-google-oauth 등이 있으며, token 인증에 사용되는 passport-jwt가 있다.

다음 포스팅에서는 passport-jwt에 대하여 알아본다.