Apollo GraphQLに入門してみた(1)、Apollo GraphQLに入門してみた(2)の続きです。今回はApollo GraphQサーバーにログイン(認証、承認、セッション管理)機能を追加してみました。
またPostgreSQL接続ライブラリーを変更しました
前回 node-postgres(pg)からPostgres.js(postgres)に変更しました。Postgres.jsには不満はないのですが、セッション管理のライブラリー等を調べると新参者のPostgres.jsをサポートしているものはありません。その中でpg-promiseを発見しました!
Postgres.jsほどモダンではありませんが、node-postgres(pg)の以下の問題は解決されています。
- API, クラス構成が微妙
- 簡単にログが取れない
- SQL文にINが書けない
さらにselect用のAPIが戻り値の件数に対し複数あるのも少し便利です、もちろんTypeScriptにも対応しています。
- none: 戻り値なし、件数が0でなければエラー
- one: 戻り値が1つ(配列ではなく変数で受け取れる)、件数が1以外ならエラー
- oneOrNone: 戻り値が1つ(配列ではなく変数で受け取れる)、件数が0ならnullを戻す、件数が1以上ならエラー
- many: 戻り値が複数(配列)、件数が0ならエラー
- any(manyOrNone): 戻り値が複数(配列)、件数が0なら空の配列を戻す
ということで、Postgres.jsからpg-promiseに変更しました 😅
Mutationを追加
前回まではGraphQL APIは情報取得のQueryのみでしたが、今回は変更系のMutationを追加してみました。ログイン認証用のuserLogin, userLogout, userRegisterをす(一部未実装)。
- スキーマ定義
const schema = gql`
・・・
type Mutation {
userLogin(email: String!, password: String!): User
userLogout: Void
userRegister(user: UserInput!): User
}
・・・
input UserInput {
name: String!
email: String!
password: String!
group_id: Int!
}
・・・
userLogout(): Void
と書いてエラーになり悩みました、引数無しの場合は括弧も書かないんですね。
また戻り値無しの場合の書き方も悩みました、Stack Overflowにnullでも良いBoolean
と書いたら?とありましたが、graphql-scalarsにVoidが在ったので、Voidを採用しました。
input UserInput { ... }
はuserRegisterで使うユーザー情報の型です。inputはこのように引数でしか使わないデータの型定義です。
- Resolver
type UserLoginArgs = {email:string, password:string}
const resolvers:Resolvers = {
Query: {
・・・
},
Mutation: {
userLogin: async (_parent, {email, password}: UserLoginArgs, context) => {
const user = await context.db.oneOrNone(
'SELECT * FROM users WHERE email = $1', email)
if (user && bcrypt.compareSync(password, user.password)) {
context.req.session.userId = user.id
return user
} else {
return null
}
},
userLogout: async (_parent, _args, context) => {
context.req.session.destroy((err: Error) => console.log("---- logout"))
},
userRegister: async (_parent, args, context) => {
// 未実装
}
}
};
ResolverはQueryと同じように書けます。パスワード認証には、攻撃に強いnode.bcrypt.jsを使う事にしました。
セッション管理用のAPIに付いては次回に説明します。
セッション管理はJWTかcookieか?
GraphQLでの認証やセッション管理を調べるとJWT(JSON Web Token)の記事がたくさんできてきます。
JWTはセッション管理やその他情報の入ったJSONを暗号化し、その文字列をHTTP通信のAuthorizationヘッダーに使う事でセッション管理を行います。
従来のCookieセッション管理と比較しJWTの良い点は、
- 格納できる情報量に制限がない(?)
- CORS(Cross-Origin Resource Sharing) 違うドメイン名間での通信でも使える
- CSRF(Cross-Site Request Forgeries) 攻撃は利用されにくい
- JWT情報の改ざんが検出できる
などがあります。
ただし、JWT情報はクライアント側で保存し、通信毎に送る必要があります。スマフォアプリ等であれば安全にJWT情報を格納できると思いますが、Webアプリの場合に使える保存先としてはLocalStorageくらいで、これを使う例をネット等に見かけますが・・・
LocalStorageはJavaScriptから読み出せるのでアプリにXSS等の脆弱性があると読み出せる可能性があります。またブラウザーのデベロッパー・ツールを使うと簡単に取得できます。
それに対しCookieでのセッション管理は、以下の対策をすれば安全に運用できます。
- CORS対策を適切に行い、APIサーバーとクライアントWebアプリのダウンロードサイトのみでCookieやJavaScriptの通信できるようにする
- セッション用クッキーにはhttpOnly属性を付けJavaScriptからはアクセスを禁止する
- 通信はHTTPSを使う
- GraphQLを使う(GraphQLはCORSに対応したJSON通信なのでCSRFは起きないらしいです)
ということで、今回はJWTではなく、古典的なCookieでセッション管理を行うことにしました。
これにより、Express用につくられたセッション管理ライブラリーが使えるようになりました。余計なコードを書かないだけでなく、すに実績のある安全なセッション管理ライブラリーを使えるのは嬉しいことです。😁
次回へ続きます・・・