バージョン情報
"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