GraphQL Express 인증구현

bmart 프로젝트에서는 REST API가 아닌 GraphQL으로 서버를 개발했습니다.

이번 프로젝트에서 jwt를 사용해서 Authentication한 방식을 정리해 봤습니다.

Send Authorization in Client

로그인을 통해 발급받은 jwt를 브라우저의 localStorage에 token이라는 key로 저장했다고 가정하겠습니다.

이제 GraphQL 서버에 요청을 보낼 때 마다, Authorization 정보를 보내주어야합니다. 방법은 간단합니다.

HTTP Request Header의 Authorization 속성에 Bearer my_token 으로 token 값을 보내주면 됩니다.

Apollo Client를 이용하면 아래와 같이 간단하게 보낼 수 있습니다

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: '/graphql',
});

const authLink = setContext((_, { headers }) => {
  // get the authentication token from local storage if it exists
  const token = localStorage.getItem('token');
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

Authenticate in Server

서버에서는 Request Header의 Authorization 속성으로부터 토큰을 파싱해와서 어떤 유저인지 식별할 수 있습니다.

(여기서 jwt 를 sign 할 때, 유저의 id를 payload로 담아 두었다고 가정합니다)

function isLoggedIn(req, res, next) {
  const token = req.headers.authorization.split(' ')[1]
  if (!token) {
    res.status(401).json({ message: 'Unauthorized' })
  }

  const isValid = jwt.verify(token, process.env.JWT_SECRET)
  if (!isValid) {
    res.status(403).json({ message: 'Forbidden' })
  }

  const userId = jwt.decode(token).id
  res.locals.userId = userId
  next()
}

인증정보를 확인하는 isLoggedIn 미들웨어를 간단하게 작성해 보았습니다. 먼저 토큰이 요청에 토큰이 담겨있는지 검사합니다. 토큰이 있다면 위변조가 되진 않았는지 유효성을 검사합니다.

유효성 검사까지 완료되면 decode해서 얻어 낸 user의 식별자를 response object에 담아줍니다. 이렇게 response object에 담아주면 다음 express middleware에서 userId (요청을 보낸 유저) 를 알 수 있습니다.

const { graphqlHTTP } = require('express-graphql')
const { isLoggedIn } = require('../util/auth')

router.post(
  '/graphql',
  isLoggedIn,
  graphqlHTTP((req, res) => {
    return {
      schema,
      context: { req, res },
    }
  })
)

위에서 구현한 isLoggedIn 미들웨어를 /graphql 엔드포인트 앞에 붙임으로서 요청을 보낸 유저에게 권한이 있는지 확인하고, 요청을 보낸 유저를 식별할 수 있습니다.