본문 바로가기
공부/express

[Express] passport 모듈을 통해 jwt 인증, 인가 구현

by 웅대 2024. 5. 21.
728x90
반응형

https://growth-coder.tistory.com/278

 

[Express] 쿠키 세션 방식으로 로그인 구현

이번 포스팅에서는 쿠키, 세션 방식으로 로그인을 구현해보려고 한다. 세팅 패키지를 설치하자. npm install express express-session bcrypt dotenv passport passport-local mongoose express : express 서버express-session :

growth-coder.tistory.com

 

이전 포스팅에서 passport 모듈을 사용해서 쿠키, 세션 방식으로 인증, 인가를 구현해보았다.

 

이번에는 passport 모듈을 사용해서 jwt 방식으로 인증, 인가를 구현해보려고 한다.

 

이번에 사용할 주요 모듈은 다음과 같다.

  1. passport : 인증을 도와주는 모듈 
  2. passport-local : 로컬 전략
  3. passport-jwt : jwt 전략
  4. jsonwebtoken : jwt 사용 도와주는 모듈

아래는 구현을 위해 사용되는 부가적인 모듈이다.

  1. express : express 서버
  2. bcrypt : 비밀번호 해시
  3. dotenv : .env 파일에 주요 정보 보관
  4. mongoose : mongoDB ORM

이전 포스팅과 전체적인 구조 자체는 비슷하다.

 

jwt 방식으로 인증, 인가 구현이 목표이기 때문에 mongoose 설정은 생략한다.

 

궁금하다면 위 "쿠키 세션으로 로그인 구현" 링크 참고

LocalStrategy 설정

<passport/LocalStrategy.js>

인증을 위한 LocalStrategy 설정부터 해보자.

const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcrypt')

const User = require('../schemas/user')
module.exports = ()=>{
    passport.use('local', new LocalStrategy({
        usernameField: 'name',
        passwordField: 'password'
    }, async(name, password, done)=>{
        try{
            const findUser = await User.findOne({name})
            if (findUser){
                const res = await bcrypt.compare(password, findUser.password)
                if (res){
                    done(null, findUser)
                } else{
                    done(null, false, {message: "비밀번호가 일치하지 않습니다."})
                }
            } else{
                done(null, false, {message: "해당 유저가 존재하지 않습니다."})
            }
        } catch(error){
            console.log(error)
        }
    }))
}

 

유저 이름과 비밀번호를 가져와 검증하는 코드로 세션 방식과 코드가 동일하다.

JwtStrategy 설정 

<passport/JwtStrategy.js>

const passport = require("passport");
const dotenv = require("dotenv");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const User = require("../schemas/user");
dotenv.config();

const options = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET,
};

module.exports = () => {
  passport.use(
    'jwt',
    new JwtStrategy(options, async(payload, done) => {
      try{
        const findUser = await User.findById(payload.id)
        if (findUser){
          return done(null, findUser)
        } else{
          return done(null, false, {message: "해당 유저가 존재하지 않습니다."})
        }
      }
      catch (error){
        done(error, false, {message: "에러가 발생했습니다."})
      }
    })
  );
};

 

passport-jwt 모듈로부터 Strategy와 ExtractJwt를 가져온다.

 

ExtractJwt는 Authorization header로부터 jwt를 추출해준다.

 

LocalStrategy와 JwtStrategy에서는 done 함수를 호출하는데 이는 이후 passport.authenticate에서 사용한다.

 

index.js 기본 세팅

const express = require("express");
const User = require("./schemas/user");
const passport = require("passport");
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
const connect = require("./schemas");
const bcrypt = require("bcrypt");
const localStrategy = require("./passport/LocalStrategy");
const jwtStrategy = require("./passport/JwtStrategy");
const app = express();
dotenv.config();
connect();
localStrategy();
jwtStrategy();
app.use(express.json());
app.use(passport.initialize());
app.listen(3001, () => {
  console.log("3001번 포트 연결");
});

 

우선 유저를 저장하는 api를 만들어보자.

 

비밀번호는 bcrypt를 통해 해시 진행

app.post("/users", async (req, res) => {
  const { name, password, age } = req.body;
  const newUser = new User({
    name,
    password: await bcrypt.hash(password, 10),
    age,
  });
  const saveUser = await newUser.save();
  res.json(saveUser);
});

 

다음은 로그인 api이다. LocalStratgy를 사용한다.

 

LocalStrategy에서 done 함수가 호출되면 그 파라미터들을 받아서 사용한다.

 

app.post("/login", (req, res, next) => {
  passport.authenticate("local", (error, user, info) => {
    if (error) {
      console.error(error);
      return next(error);
    }
    if (!user) {
      return res.json(info);
    }
    return req.login(user, { session: false }, (error) => {
      if (error) {
        return next(error);
      }
      console.log(user)
      const newJwt = jwt.sign(
        {
          id: user._id,
        },
        process.env.JWT_SECRET
      );
      return res.json({ newJwt });
    });
  })(req, res, next);
});

 

이전 포스팅과 달라진 점은 req.login을 호출할 때 { session: false} 옵션 값을 사용하여 세션에 값을 저장하지 않도록 한 점이다.

 

그리고 이전 포스팅에서는 직렬화와 역직렬화를 구현하였는데 이번 포스팅에서는 세션을 사용하지 않고 jwt로부터 유저 정보를 얻어낼 것이기 때문에 구현할 필요가 없다.

 

그리고 로그인에  성공하면 jwt를 반환하도록 구현하였다.

 

다음은 인가를 구현해보자.

 

jwt를 받아서 그 jwt가 유효하다면 req.user에 유저 정보를 넣어주고 그렇지 않으면 에러를 다음 미들웨어로 넘겨주는 미들웨어를 구현해보자.

 

const jwtAuthorize = (req, res, next)=>{
  passport.authenticate("jwt", { session: false }, (error, user, info) => {
    console.log(error)
    console.log(info.message)
    if (error){
        return next(error)
    }
    if (!user){
      return res.json({message : info.message})
    }
    req.user = user
    next()
  })(req, res, next);
}

 

이제 인가가 필요한 api가 있다면 이 미들웨어를 적용시키고 req.user를 통해 원하는 데이터를 가져오면 된다.

 

토큰을 보내면 자신의 정보를 가져오는 api를 만들어보자.

app.get("/my-info", jwtAuthorize, (req, res)=>{
  res.json(req.user)
});

 

<index.js 전체코드>

const express = require("express");
const User = require("./schemas/user");
const passport = require("passport");
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
const connect = require("./schemas");
const bcrypt = require("bcrypt");
const localStrategy = require("./passport/LocalStrategy");
const jwtStrategy = require("./passport/JwtStrategy");
const app = express();
dotenv.config();
connect();
localStrategy();
jwtStrategy();
app.use(express.json());
app.use(passport.initialize());
app.listen(3001, () => {
  console.log("3001번 포트 연결");
});

app.post("/users", async (req, res) => {
  const { name, password, age } = req.body;
  const newUser = new User({
    name,
    password: await bcrypt.hash(password, 10),
    age,
  });
  const saveUser = await newUser.save();
  res.json(saveUser);
});

app.post("/login", (req, res, next) => {
  passport.authenticate("local", (error, user, info) => {
    if (error) {
      console.error(error);
      return next(error);
    }
    if (!user) {
      return res.json(info);
    }
    return req.login(user, { session: false }, (error) => {
      if (error) {
        return next(error);
      }
      console.log(user)
      const newJwt = jwt.sign(
        {
          id: user._id,
        },
        process.env.JWT_SECRET
      );
      return res.json({ newJwt });
    });
  })(req, res, next);
});
const jwtAuthorize = (req, res, next)=>{
  passport.authenticate("jwt", { session: false }, (error, user, info) => {
    console.log(error)
    console.log(info.message)
    if (error){
        return next(error)
    }
    if (!user){
      return res.json({message : info.message})
    }
    req.user = user
    next()
  })(req, res, next);
}
app.get("/my-info",jwtAuthorize, (req, res)=>{
  res.json(req.user)
});

 

결론

  1. passport-local을 사용하여 로그인(인증) 전략을 구현한다.
  2. passport-jwt를 사용하여 인가 전략을 구현한다.
  3. passport.authenticate를 통해 상황에 맞는 전략을 선택한다.
  4. jwt를 사용할 땐 authenticate의 두 번째 파라미터로 {session:false}를 넣어준다.
728x90
반응형

댓글