WEB

【Redis・TypeScript】GraphQLで作るAPIサーバ入門・セッションの導入

WEB
この記事は約7分で読めます。

バージョン情報

"devDependencies": {
    "@types/connect-redis": "^0.0.14",
    "@types/express": "^4.17.8",
    "@types/express-session": "^1.17.0",
    "@types/node": "^14.14.5",
    "@types/redis": "^2.8.28",
    "nodemon": "^2.0.6",
    "ts-node": "^9.0.0",
    "typescript": "^4.0.5"
  },
  "dependencies": {
    "@mikro-orm/cli": "^4.2.3",
    "@mikro-orm/core": "^4.2.3",
    "@mikro-orm/migrations": "^4.2.3",
    "@mikro-orm/postgresql": "^4.2.3",
    "apollo-server-express": "^2.18.2",
    "argon2": "^0.27.0",
    "class-validator": "^0.12.2",
    "connect-redis": "^5.0.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "graphql": "^15.4.0",
    "pg": "^8.4.2",
    "redis": "^3.0.2",
    "reflect-metadata": "^0.1.13",
    "type-graphql": "^1.1.0"
  },

はじめに

前回作成したGraphQL APIにセッション管理を導入します。

前回: 【GraphQL・TypeScript】GraphQLで作るAPIサーバ入門

Redisを使用したセッション管理

セッション管理には、Redisというインメモリ方式のKey-Valueストアを使用します。
これには、以下のようなメリットがあります。

インメモリのため、処理が早い。
クライアントにはIDしか保存しないため、情報漏洩のリスクが低い。
エクスパイア処理を1行の設定で出来る。

※参考: Railsのセッション管理には何が最適か
https://qiita.com/shota_matsukawa_ga/items/a21c5cf49a1de6c9561a

Redisのインストールと起動

brew install redis
redis-server

ライブラリのインストール

yarn add redis connect-redis express-session
yarn add -D @types/express-session @types/redis @types/connect-redis

Expressでセッションを使用するよう設定

import redis from 'redis';
import session from 'express-session';
import connectRedis from 'connect-redis';
import { MyContext } from './types';


const main = async () => {

  /* Expressサーバーの作成 */
  const app = express();

  const RedisStore = connectRedis(session);
  const redisClient = redis.createClient();

  app.use(
    session({
      name: 'セッションID名',
      store: new RedisStore({
        client: redisClient,
        disableTouch: true,
      }),
      cookie: {
        maxAge: 1000 * 60 * 60 * 24 * 365, // 1年
        httpOnly: true,
        secure: __prod__, // coolie only works with https
        sameSite: 'lax', // csrf
      },
      saveUninitialized: false,
      secret: '任意の文字列',
      resave: false,
    }),
  );

  /* ApolloServerの作成 */
  const apolloServer = new ApolloServer({
    schema: await buildSchema({
      resolvers: [PostResolvers, UserResolvers],
      validate: false,
    }),
    context: () => ({ em: orm.em }),

    // リクエストとレスポンスを扱えるよう設定
    context: ({ req, res }): MyContext => ({ em: orm.em, req, res }),
  });

コンテクストの型情報を修正

コンテクストとして、リクエストとレスポンスを扱えるよう設定したので、型を設定します。

import { EntityManager, IDatabaseDriver, Connection } from '@mikro-orm/core';
import { Request, Response } from 'express';

export type MyContext = {
  em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
  req: Request;
  res: Response;
};

リゾルバの設定

ログイン時に、セッションを保存するようにします。

@Mutation(() => UserResponse)
  async login(
    @Arg('options') options: UsernamePasswordInput,
    @Ctx() { em }: MyContext,
    @Ctx() { em, req }: MyContext,
  ): Promise<UserResponse> {
    const user = await em.findOne(User, { username: options.username });

    ...省

    // ユーザーidをセッションデータとして保存
    req.session!.userId = user.id;

    return { user };
  }

実行

実行すると

  • ブラウザのセッションストレージに、セッションデータが保存される
  • セッションのKey: 設定したキー名
  • セッションのValue: ユーザーIDを暗号化したもの
  • ログイン後のユーザーリクエストにはセッションデータが入っているので、それを用いて認証済ユーザーかどうか判定できる

まとめ

  • セッション管理に適したデータストアを選択できる
  • Redisを使用することで、高速で比較的セキュアなセッション管理ができる
  • Apolloのコンテキストとして、リクエストとレスポンスを扱うことができる
  • GraphQLのリゾルバで、リクエストとレスポンスに含まれるセッションデータを扱うことができる

参考

Railsのセッション管理には何が最適か – Qiitahttps://qiita.comFullstack React GraphQL TypeScript Tutorialhttps://youtu.be