본문 바로가기
웹 개발

passport-jwt 로 토큰인증하기

by 주식 키우는 개발자 2020. 11. 7.
반응형

안녕하세요. 오늘은 서비스에서 현재 유저가 로그인이 되어있는지 확인할 수 있는 방식 중에서 토큰을 사용해서 인증하는 방식을 알아보려고 합니다. 

두가지 라이브러리를 사용해서 토큰인증해보겠습니다. 

- jsonwebtoken :  암호화된 토큰을 사용하기 위한 라이브러리 

- passport : passport는 jwt인증을 쉽게 할 수 있도록 도와주는 라이브러리

 

1. 설치

yarn add passport passport-jwt

 

 

src/passport.js

import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";

공식사이트를 참고해서 passport-jwt의 Strategy와 ExtractJwt를 사용해줍니다.

2. jwtOptions

아래 Options 는 jwttoken 에 대한 옵션을 가지고 있는 변수입니다. 

const jwtOptions = {
  // header에 bearer스키마에 담겨온 토큰 해석할 것
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  // 해당 복호화 방법사용
  secretOrKey: process.env.JWT_SECERT
};

 

첫번째 옵션은 header의 bearerToken을 해석할 것이라는 옵션이고, 두번째는 암호 해독 방식을 알려주는 설정입니다.

2.1 secretOrKey 설정하기

secretOrKey 를 사용하기 위해서는 암호 해독 방식 랜덤키를 사용해야합니다.

랜덤키를 생성하는 방법은 많지만 그 중에서 저는 randomkeygen 이라는 사이트에서 사용하려고 합니다. 

3. passport.use()

 

설정이 완료되었기 때문에 passport 를 사용해야합니다. 

passport.use(new JwtStrategy(jwtOptions, verifyUser));
passport.initialize();

 

첫번째 인자로는 옵션을 넣어주고, 두번째 인자로는 verifyUser라는 복호화 성공시 호출할 함수를 적어줍니다.

4. callback 함수

const verifyUser = async (payload, done) => {
  console.log("payload", payload);
  try {
    const user = await prisma.user({ id: payload.id });
    // user가 있을 경우
    if (user) {
      return done(null, user);
    } else {
      return done(null, false);
    }
  } catch (error) {
    return done(error, false);
  }
};

payload의 id를 가진 user가 존재한다면 클라이언트에서 요청한 req에 담겨있던 token이 유효한 토큰이라는 것이기 때문에 로그인이 되어있다고 할 수 있습니다. 그래서 return done(에러 , 유저)를 상황에 맞게 하면 됩니다.

전체 코드

import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import "./env";
import { prisma } from "../generated/prisma-client";

// passport-jwt인증에 사용할 옵션
const jwtOptions = {
  // header에 bearer스키마에 담겨온 토큰 해석할 것
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  // 해당 복호화 방법사용
  secretOrKey: process.env.JWT_SECERT
};

// 인증 성공시 콜백함수
const verifyUser = async (payload, done) => {
  console.log("payload", payload);
  try {
    const user = await prisma.user({ id: payload.id });
    // user가 있을 경우
    if (user) {
      return done(null, user);
    } else {
      return done(null, false);
    }
  } catch (error) {
    return done(error, false);
  }
};


// jwtOptions를 기반으로한 jwt전략으로 인증하고 성공시  verifyUser호출
passport.use(new JwtStrategy(jwtOptions, verifyUser));
passport.initialize();

여기까지 한 것을 요약해보자면,

  1. 클라이언트에서 유저가 Header에 Token을 담아서 request요청을 보냄
  2. 서버에서 미들웨어를 통해 passport가 JWT을 해석함. (해석해서 나오는 값은 User테이블의 id)
  3. 입력받은 id값을 가지고 있는 User가 있는지 확인하여 동작
  4. 있으면 done(null,user) 리턴해줌.

이제 리턴해준 user를 가지고 서버로 보내고 있던 request객체에 같이 실어주는 부분이 필요합니다.

5. 미들웨어 사용하기

저는 graphql서버를 만들었기 때문에 express.use를 통해서 서버에 접근할 수 있습니다. 미들웨어를 통해서 JWT토큰을 인증했을 때 request객체에 user정보를 실어서 서버에게 주는 부분을 작성해보겠습니다.

src/server.js

import logger from "morgan";
import { GraphQLServer } from "graphql-yoga";
import schema from "./schema";
import passport from "passport";
import "./passport";
import { authenticateJWT } from "./passport";
import "./env";
const PORT = process.env.PORT || 4000;

const server = new GraphQLServer({
  schema,
  context: ({ request }) => ({ request })
});
server.express.use(logger("dev"));
server.express.use(authenticateJWT);
server.start({ port: PORT }, () =>
  console.log(`server is running http://localhost:${PORT}`)
);

위처럼 AuthenticateJWT라는 JWT인증 함수를 서버가 켜지기 전에 호출했습니다. 그리고 ./passport를 import함으로써 passport.use()와 passport.initialize()를 실행시킬 수 있습니다.

그러면 authenticateJWT를 어떻게 작성했는지 보겠습니다.

 

 

src/passport.js

// verifyUser가 호출되고난 후 passportdlswmd
export const authenticateJWT = (req, res, next) =>
  passport.authenticate("jwt", { sessions: false }, (error, user) => {
    //verifyUser에서 user를 찾았다면 서버에게 요청하는 req객체의 user에 담아서 서버에게 넘겨줌
    if (user) {
      req.user = user;
    }
    next();
  })(req, res, next);

미들웨어이기 때문에 req,res,next를 파라메터로 받습니다. passport의 authenticate라는 메소드는

첫번째로 어떤 인증을 할 것인지 적어줍니다. 저는 jwt를 인증을 할 것이기 때문에 "jwt"를 적었습니다.

두번째는 session은 사용하지 않는다고 적었습니다.

세번쨰는 verifyUser를 통해서 return 된 error와 user를 파라메터로 하는 함수를 작서하는 부분입니다. 저는 user가 있을 경우에 request객체에 user라는 키값으로 데이터를 넣어서 보내야 하기 때문에 위와 같이 user가 있을 경우 미들웨어를 통해 접근할 수 있던 req객체의 user에 해당 user를 집어넣었습니다.

위와 같이 작성하게 될 경우, 프론트엔드에서 보낸 JWT토큰을 해석해서 user정보를 확인했는데 존재하는 user라면 서버에게 보내는 req객체에 user정보를 넣어서 보낼 수 있습니다. 이렇게 하게 되면 로그인이 필요한 기능에 대해서 쉽게 접근을 제어할 수 있습니다.

playground에서 header에 대한 부분을 실험하기 위해서는 아래와 같이 사용해야합니다.

 

6. 토큰 발급받기

위의 과정은 토큰을 프론트엔드에서 붙혀서 보냈을 경우 어떻게 처리하는지에 대해서 정리한 것입니다. 하지만 사용자가 로그인에 성공했을 경우 최초 토큰은 서버에서 만들어서 사용자에게 보내주어야합니다.

jsonwebtoken을 만들기 위해 설치를 하겠습니다.

yarn add jsonwebtoken

jwt.sign("token으로 바꿀 데이터" , "암호화 방법")

저는 아래와 같이 작성했습니다.

import jwt from "jsonwebtoken";
import "./env";

// id를 받아서 암호화된 JWTToken을 만들어주는 메소드
export const generateJWTToken = id => jwt.sign({ id }, process.env.JWT_SECERT);

저는 .env파일을 만들어서 env 설정을 매번 해주지 않도록 작성했습니다.

./env

import path from "path";
require("dotenv").config({ path: path.resolve(__dirname, ".env") });

이렇게 작성하면 이제 로그인에 성공했을 경우 사용자에게 token을 발급해주게 됩니다.

7. 인증하기

이제 개발한 것을 통해 실제로 사용해보겠습니다. allUser Query를 사용하기 위해서는 로그인이 되어있어야 한다고 가정해보겠습니다.

src/middleware.js

// request에 user정보가 없으면 error를 return
// 로그인이 필요한 query , mutation에서 사용하면 됨.
export const isAuthenticate = request => {
  console.log(request.user);
  if (!request.user) {
    throw Error("You need to login for this action!!!");
  }
};

src/api/User/allUser/allUser.js

import { prisma } from "../../../../generated/prisma-client";
import { isAuthenticate } from "../../../middleware";

export default {
  Query: {
    allUser: async (_, args, { request }) => {
      isAuthenticate(request);
      return await prisma.users();
    }
  }
};

query의 첫번째 파라메터는 사용하지 않고, 두번째 파라메터는 사용자가 보낸 객체가 담겨 있고, 세번째는 req객체가 담겨있습니다. request객체에 user가 담겨있는지 확인하기 위해 저는 isAuthenticate라는 메소드를 만들어서 확인했습니다.

만일 로그인이 잘 되어있다면 request객체에 아래와 같이 User객체가 담겨서 올 것입니다.

만약 토큰을 정상적으로 전달하지 않았다면 아래와 같이 에러메세지를 띄울 것입니다.

'웹 개발' 카테고리의 다른 글

javascript touch event 사용해보기  (0) 2020.11.10
aws ubuntu에 war 배포 방법  (0) 2020.11.07

댓글