analogcoding

JWT in graphQL 본문

Be well coding/Learn more

JWT in graphQL

be well 2019. 7. 28. 17:37

 

GraphQL은 플랫폼 독립적이고 기존의 REST API와는 완전히 다르지만 기존의 JWT(Json Web Token)을 충분히 이용 가능하다.

 

JWT 토큰을 전달하는 위치가 헤더 에서 쿼리뮤테이션의 인자로 바뀌었을 뿐이다.

 

 

토큰을 생성(create) , 검사(decade)하는 파일 , 함수 생성

 

const jwt = require('jsonwebtoken');
require('dotenv').config();

exports.jwtsign = email => {
  const token = jwt.sign({ email }, process.env.PRIVATE_KEY, { expiresIn: '5h' });
  return token;
};

return 한 토큰을 클라이언트에 전달해주고

server.express.use((req, res, next) => {
  const { authorization } = req.headers
  jwt.verify(authorization, process.env.PRIVATE_KEY, (err, decodedToken) => {
    if (err || !decodedToken) {
      res.status(401).send('not authorized')
      return
    }
    next()
  })
})

로그인 이후 정보를 요청할 때 

 

ex)

yarn add jsonwebtoken

.env 에 JWT_TOKEN 을 추가

 

JWT.js 파일에 jwt 생성

import jwt from 'jsonwebtoken';

const createJWT = (id: number): string => {
  const token = jwt.sign(
    {
      id
    },
        process.env.JWT_TOKEN || ""
  );
  return token;
};

export default createJWT;
const resolvers = {
    Query: {
        hello: (_, { name }) => `Hello ${name || "World"}`
}
};

 

API Resolver 를 정의할 때

 

graphQL schema 정의 ( graphQL API 의 리턴타입을 정의 ) 

 

type PostDetail{
    post: Post!
    login_user: User!
}

 

getPost는 API 리졸버의 이름

type Query {
	getPost(유저id: String! , jwt_token: String!): PostDetail!
}

괄호안에는 파라미터의 이름과 그 타입이 들어가고, !는 마찬가지로 해당 파라미터는 Null일수 없음을 의미다.

Resolver 이름과 파라미터의 내용을 지키지 않고 클라이언트에서 Request를 하게 되면, 404 Error가 발생한다.
마지막으로 getPost의 리턴 타입이 PostDetail이라고 정의되어있다. 

 

Resolver 함수

module.exports = {
    Query:{
        getPost: (_, args) => {
            const {post_id, jwt_token} = args;
            //데이터베이스에서 post정보를 얻으세요.
            const post = getPostByDatabase(post_id);
            //토큰으로 부터 login user정보 id를 얻으세요.
            const loginUser_id = getUserIdByJwtToken(jwt_token);
            //데이터베이스에서 loginUser 정보를 얻으세요.
            const loginUser = getUserByDatabase(loginUser_id);
            
            //return 오브젝트 프로퍼티와 값이 정의한 PostDetail 타입과 맞아야 한다. 
            // 이 리졸버는 PostDetail을 리턴한다고 리졸버정의에서 선언했기 때문.
            
            //객체를 리턴하는 경우에는 그 리턴되는 객체의 프로퍼티도 맞아야 한다.
            return {
                post: post
                login_user: loginUser
            }
        }
    }
}

이 후 사용.

 


클라이언트에서 요청할 때

Query{
    getPost(post_id:1, jwt_token: "jwt token blah blah"){
        post{
            title
            content
            post_writer{
                user_id
                user_name
            }
        }
        login_user{
            user_id
            usesr_name
        }
    }
}

++

 

GraphQL and RESTful

File 전송과 같이 RESTful 이 더 유리한 API 가 있을 수 있고,
다양한 정보를 주고받는 것 같이 GraphQL 이 더 유리한 API 가 있을 수 있다. 이럴 때 둘 중 하나만 선택해야할 필요는 없다.
하나의 Endpoint 를 GraphQL 용으로 만들고, 다른 RESTful endpoint 들을 만들어 놓는 방법도 있다.
주의해야할 것은 하나의 목표를 위해 두 API structure 를 섞어놓는 것은 API 의 품질을 떨어트릴 수 있다는 점이다.

 


token 을 verify 하는 과정에서 토큰이 있을 때와 없을 때 , 다를 때 세 가지의 경우를 분기해주고 최종적으로 

 

필요한 부분은 resolver 함수에서 확인하는 방법 시도. 이 방법은 처음에 얘기가 나왔던 방법이지만 효율적이지 못하다고 생각했고

 

어느 부분에서 시도를 해야할 지 정확히 감을 잡지 못했었지만 결국 이 방법을 택해서 해결하게 되었다.

 

클라이언트에서 토큰에 header에 "x-jwt" 라는 키의 값으로 토큰을 담아보내면 상태에 따라 값을 리턴하는 것을 

graphQL playground 에서 확인했다.

export const decodeJWT = token => {
  return jwt.verify(token, process.env.PRIVATE_KEY, (err, decode) => {
    if (err) {
      // console.log("decode 함수에서 에러 발생");
      return err;
    }
    // console.log("decode 함수에서 에러 없음");
    return decode.nickName;
  });
};

이처럼 토큰 verify 에서 유효한 토큰인 지를 검사해주고

const JWT = async (req, res, next) => {
  const token = req.get("x-jwt");

  // 요청에서 토큰을 찾는다??
  if (!token) {
    // console.log("미들웨어에서 겟한 토큰이 없음");
    return next();
    // 없다면 패쓰
  }
  // else console.log("미들웨어에서 겟한 토큰-->", token);
  try {
    const nickName = await decodeJWT(token);
    req.nickName = nickName;
    // console.log(req.nickName);
    return next();
    // 있다면 에러처리하고 패쓰!
  } catch (error) {
    console.log("err");
    return error;
  }
};

middleware 로 토큰이 없을  때와 있을 때 , 에러가 발생할 때를 분기해서 req 에 토큰화 되기 전 값을 담아서 next() 로 넘겨주었다.

'Be well coding > Learn more' 카테고리의 다른 글

PASSPORT 카카오 (KaKao)2  (0) 2019.08.01
PASSPORT 카카오 (KaKao)  (0) 2019.07.30
Sequelize  (0) 2019.07.27
Passport start  (0) 2019.07.27
ESlint 설정 ( with babel , prettier , gitignore)  (0) 2019.07.27
Comments