GraphQLのサーバサイド認証とその実装のためのContext

新しい技術を試すにあたって、面倒ではあるが、認証までは一通り使いこなせる必要があると感じている。ただ、認証は結構めんどくさいし、すぐ忘れてしまいがちなので、メモを残しておく。

GraphQLサーバに、認証機能を付与するにあたって、Contextを定義する。このMyContextはGraphGLの各呼び出しに対して、引数のほかに共通的に呼び出される。ここでは認証を想定し、認証されていればMyContextはユーザ情報を含み、認証されていなければnullであるとする。

interface MyContext {
  user: User | null;
}

Resolverにも先ほどMyContextを設定する。/meのエンドポイントの定義の3つめの引数がMyContextとなるので、user情報を含んでいるかどうかをチェックしてエラーを投げている。エラーメッセージを適当に投げるとInternal Server Errorと扱われてしまうので、正確に認証エラーを返すには、GraphQLErrorを使ってカスタムエラーを投げることがある。

const resolvers: Resolvers<MyContext> = {
  Query: {
    me: (_parent, _args, {user}) => {
      if (user == null) throw new GraphQLError('Unauthorized', {
        extensions: { code: 'UNAUTHORIZED' }
      });
      return user
    },

もう2か所。ApolloServer生成時にもMyContextを設定しておき、startStandaloneServerのoptionsでは、設定したMyContext生成について定義する必要がある。今回はヘッダーで受け取ったAuthorizationの値からUser型のデータまたはnullを受け取り返すといった処理になっている。各ユーザ情報とAuthorizationの値とをマッチさせる返答することになる。

const server = new ApolloServer<MyContext>({
  typeDefs,
  resolvers,
});
const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async ({req}) => {
    const token = req.headers.authorization || '';
    const user = getUser(token);
    return { user } satisfies MyContext;
  },
});