엘리스 AI 트랙 4기/elice AI track

[6주차] Passport.js와 로그인 구현하기

_sweep 2022. 2. 23. 15:44

2월 23일 자 학습 내용 정리입니다.

 

 

 Passport.js

Passport.js는 Express.js 어플리케이션에 간단하게 사용자 인증 기능을 구현하게 도와주는 패키지이다.
유저 세션 관리 및 다양한 로그인 방식 추가 기능을 지원한다.

 

Passport.js는 다양한 로그인 방식을 구현하기 위해 strategy라는 인터페이스를 제공한다.
strategy 인터페이스에 맞게 설계된 구현체를 그대로 passport에 붙이면 passport는 로그인 구현을 도와준다.
passport-google-oauth, passport-facebook, passport-twitter, passport-kakao, passport-naver 같이 SNS 계정을 통해서 바로 로그인할 수 있는 패키지 등 다양한 구현체들이 이미 존재하는데 그중 passport-local은 username, password를 사용하는 로그인의 구현체이다.

 

 

✏️ 예제 - 로그인 구현하기

로그인을 구현하는 방식은 다음과 같다.

  • 로그인 화면 구성
  • passport-local strategy로 로그인 구현
  • passport.js 설정
  • passport로 요청 처리

 

✔️ 로그인 화면 구성

...
form(action="/auth" method="post" onsubmit="return check()")
  table
    tbody
      tr
        td 이메일
        td: input(type="text" name="email")
      tr
        td 비밀번호
        td: input(type="password" name="password")
      tr
        td(colspan="2")
        td: input(type="submit" name="로그인")
...

 

로그인 페이지를 구현한 코드이다.

POST 요청을 보내기 위해 form을 사용했고 입력받을 이메일, 비밀번호 칸을 생성했다.

 

✔️ 유효 값 체크

...
script.
  function check() {
    const email = document.querySelector('[name="email"]').value;
    if (!email) {
        alert("이메일을 입력해 주세요.");
        return false;
    }
    
    const password = document.querySelector('[name="password"]').value;
    if (!password) {
        alert("비밀번호를 입력해 주세요.");
        return false;
    }
    return true;
 }

 

로그인 시에는 이메일과 비밀번호의 입력이 꼭 필요하기 때문에 이메일과 비밀번호를 둘 다 입력받았는지 확인한다.

 

✔️ strategy 생성

const config = {
  usernameField: "email",
  passwordField: "password",
};

const local = new LocalStrategy(config, async (email, password, done) => {
  try {
    const user = await User.findOne({ email });
    if (!user) {
      throw new Error("회원을 찾을 수 없습니다.");
    }
    if (user.password !== password) {
      throw new Error("비밀번호가 일치하지 않습니다.");
    }
    
    done(null, {
      shortId: user.shortId,
      email: user.email,
      name: user.name,
    });
  } catch (err) {
    done(err, null);
  }
});

 

passport-local을 사용하기 위해서는 먼저 Strategy를 생성하는데 이때 config 값을 전해준다.

passport-local이 username과 password를 사용해 사용자를 인증하기 때문에 인증할 때 쓰일 username이 email이고 password가 password임을 알려주는 것이다.

 

Strategy 생성 시 콜백함수에는 사용자로부터 받은 email, password 그리고 콜백함수인 done이 인자로 주어진다.

유저의 아이디가 있고 해당 유저의 비밀번호도 일치한다면 로그인은 성공한다.
로그인이 성공할 경우 done 함수의 첫 번째 인자는 null, 두 번째 인자는 입력받은 사용자의 정보를 전해준다.
로그인이 실패할 경우 done에 err를 넘겨준다.

 

쉽게 말해 done의 첫 번째 인자는 에러 발생 여부, 두 번째 인자는 사용자 정보의 전달 여부라고 생각하면 된다.

 

✔️ passport.js 설정

const local = require('./strategies/local');
passport.use(local);

 

작성한 strategy를 사용할 수 있도록 passport.use로 선언해야 한다.
passport.use로 strategy를 사용할 수 있게 선언한 뒤 passport.authenticate를 사용해 해당 strategy로 로그인 요청을 처리할 수 있다.

 

이 경우 passport.use 부분과 strategy 생성 부분을 합쳐 작성하기도 한다.

 

passport.use(new LocalStrategy(
  async function(username, password, done) {
    await User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      if (!user.verifyPassword(password)) { return done(null, false); }
      return done(null, user);
    });
  }
));

 

✔️ 로그인 요청 처리

--- routes/auth.js ---
router.post("/", passport.authenticate("local"));

--- app.js ---
const session = require("express-session");

app.use(
  session({
    secret: "secret",
    resave: false,
    saveUninitialized: true,
  }),
);
app.use(passport.initialize());
app.use(passport.session());
app.use("/auth", authRouter);

 

passport.authenticate 함수를 http 라우팅에 연결하면 passport가 자동으로 해당 strategy를 사용하는 request handler를 생성한다.
이때 express-session과 passport.session()을 사용하면 passport가 로그인 시 유저 정보를 세션에 저장하고 가져오는 동작을 자동으로 수행해 준다.

 

✔️ session 활용

passport.serializeUser((user, callback) => {
  callback(null, user);
});
passport.deserializeUser((obj, callback) => {
  callback(null, obj);
});

 

session을 이용해 user를 사용할 때에는 serializeUser와 deserializeUser를 설정해 주어야 한다.

serializeUser는 user 객체를 전달받아 세션에 저장한다.

이로 인해 페이지 이동 시에도 로그인 정보가 유지될 수 있다.

deseializeUser는 세션에 저장된 정보를 통해 user 정보를 불러온다.

 

✔️ 로그아웃

router.get('/logout', ... {
    req.logout();
    res.redirect('/');
});

 

로그아웃의 경우 req.logout() 함수를 통해 세션의 로그인 정보를 삭제하여 구현할 수 있다.

 

✔️ 로그인 확인 미들웨어

function loginRequired(req, res, next) {
  if (!req.user) {
    res.redirect("/");
    return;
  }
  next();
}
app.use("/posts", loginRequired, postsRouter);

 

로그인 확인 미들웨어는 특정 구역에 로그인을 필수로 설정하고 싶을 경우 미들웨어를 사용하여 로그인 여부를 체크한다.

 

 

🔍 참조

passport로 로그인하기 https://www.zerocho.com/category/NodeJS/post/57b7101ecfbef617003bf457

passport-local https://www.passportjs.org/packages/passport-local/