しんのきです。
Hasura の Tips 的なやつをバンバン書いていきたいのですが、とりまとめがないのでしばらく個人ブログに書きたいことを書いていきます。
概要
例としてログイン済のユーザーが記事を投稿でき、ログインしていないユーザーでも記事を閲覧できるようなサービスを考えます。
テーブル設計は単純に以下のようになります。
テーブル設計が済んだところで、 Hasura ではこのようなサービスを Role と Permission を設定することによって直感的に表現できます。
Role と Permission を用いて表現すると、
user
およびanonymous
Role はuser
およびpost
を自由に select できるuser
Role はuser.id
もしくはpost.user_id
が自分のユーザーIDと一致する場合に insert / update / delete できる
というように表現されます。
Role の名前は自由に決めることができますが、今回のような場合 user
と anonymous
というように名付けると分かりやすいです。
例えば post
の Permission は以下のように設定できます。
✔ になっているのがフルアクセス許可、 ✘ はアクセス拒否、フィルターっぽいアイコンになっているのが条件付きのアクセス許可です。
細かくは割愛いたしますが post.id
はデータベース側で自動生成、 post.user_id
は毎回 mutation で渡さないでも自分のユーザーIDが自動で入ってくれるような設定にしているので user
からは insert できないようにしています。
どのように Role が決定されるのか
Hasura にアクセスする際の Role は以下のいずれかの方法によって決定されます。
Role の機能を使うには前提として HASURA_GRAPHQL_ADMIN_SECRET
の設定が必須なので注意してください。
JWT 認証
Authentication using JWT | Hasura GraphQL Docs
登録された JWKs にマッチする JWT が Authorization: Bearer
ヘッダーにセットされた場合、その JWT の中身が使われます。デフォルトでは https://hasura.io/jwt/claims
というネームスペースの JWT claim の中身が利用されますがこのネームスペースは設定により変更可能です。
この JWT を持ったアクセスは x-hasura-allowed-roles
で指定された Role になることができ、デフォルトでは x-hasura-default-role
で指定された Role が使用されますが x-hasura-role
ヘッダーを追加で渡すことで x-hasura-allowed-roles
で許可された Role であれば使用できます。
他にも x-hasura-*
で始まる名前の claim が渡されると Hasura でパースされ Session Variables として Permission Rule や Column Presets 内で利用が可能です。
JWT の sub
を x-hasura-user-id
にセットするという使い方が多いです。
webhook 認証
Authentication using webhooks | Hasura GraphQL Docs
登録されたエンドポイントに問い合わせ、レスポンスとして Role や Session Variables を返却してもらいます。
基本的に JWT 認証のほうがシンプルでパフォーマンスも良いはずなので、特別な理由がない限りまずは JWT 認証を検討するのがおすすめです。
匿名アクセス
Unauthenticated / Public access | Hasura GraphQL Docs
JWT もしくは webhook で認証できなかった場合、 HASURA_GRAPHQL_UNAUTHORIZED_ROLE
がセットされていればその Role が使われます。
任意の Role が設定できますが、基本的には anonymous
という名前を使うことになるでしょう。
Admin アクセス
リクエストヘッダーに正しい x-hasura-admin-secret
がセットされた場合は今までの全ての設定が無視され、デフォルトで admin
という Role が適用されます。
admin
は最初から存在する特別な Role で、全ての Permission が許可され全てのデータに自由にアクセスできます。
Hasura Console はこの Admin アクセスを利用しています。
このとき同時にリクエストヘッダーに x-hasura-*
で始まるヘッダーを渡すことで任意の Session Variables を渡すことができ、その中でも x-hasura-role
を渡すことによって任意の Role になることができます。
Role ごとに見えるスキーマそのものが変わる
Hasura の面白い挙動の一つだと思うのですが、Role と Permission が設定されると権限のないテーブルやフィールドはただアクセスできないだけではなくその Role から見みたスキーマから消失します。
これと先ほどみた x-hasura-admin-secret
と x-hasura-role
を同時に渡すと任意の Role になれるということを知っていると便利な使い方ができます。
Hasura Console で Role ごとのクエリを試す
Hasura Console の GraphiQL のヘッダーに x-hasura-role
を追加してあげると、任意の Role になりきってクエリを試すことができます。
分かりやすい例でいうと今回のサンプルプロジェクトで Hasura Console を開き GraphiQL のヘッダーに x-hasura-role: anonymous
を指定すると、 anonymous
Role には実行できる mutation が1つもないので右に表示される Docs から mutation そのものがその場で消えます。
また Admin アクセスでは 任意の Session Variables を渡すことができるので、 x-hasura-role: user
と x-hasura-user-id
を渡すことで任意の user
になりすましてクエリの実行が可能です。
GraphQL Code Generator で Role ごとのコードを自動生成する
今回一番伝えたかったのがこれです。これは非常に強力です。
Hasura と GraphQL Code Generator を組み合わせて使うとき、 HASURA_GRAPHQL_ADMIN_SECRET
をセットしている場合は codegen.yml
のほうにも x-hasura-admin-secret
をセットしてあげる必要があります。このとき x-hasura-role
を一緒に渡してあげることで特定の Role のスキーマに沿って codegen できます。
schema: - http://localhost:8080/v1/graphql headers: x-hasura-admin-secret: myadminsecretkey x-hasura-role: user
この設定をするメリットは大きく二つあり、一つ目は Role ごとに必要な型定義のみが生成されることです。例えば最初に見た例で insert 権限のない id
フィールドを誤って渡したりするようなことはなくなりますし、 codegen されたファイルの行数もスッキリします。
もう一つが権限のないフィールドを読みに行こうとすると codegen に失敗することです。codegen に成功し TypeScript のエラーが出ていないという時点で通信まわりでフロントエンドがクラッシュしないことがかなりの精度で保証されるため、開発効率が非常に良いですしリファクタリングもやりやすくなります。
1つのクライアントに対して極力1つの Role にしたほうがいい
下のドキュメントなどを見ると author
reviewer
editor
というように細かく Role を分けていますが、少なくとも Apollo Client からアクセスする場合はこのような分け方は個人的におすすめしません。
Access control examples | Hasura GraphQL Docs
これは Apollo Client のキャッシュの仕組みに関係しており、上で見たようにヘッダーを動的に変えるとスキーマそのものが変化しますが、同じ client
でこれをやってしまうと異なるスキーマのデータが同じキャッシュに保存されることになってしまうためです。
また issue はあがっていますが現状 Role 間での Permission の継承などはできないため、似たような Role であっても全て個別に設定する必要があります。
Can role permissions of hasura be inherited? · Issue #611 · hasura/graphql-engine · GitHub
まずは Permission Rule を使って1つの Role でうまく表現できないかを検討してみるのがいいと思われます。
Apollo Client を使っていて、どうしてもページによって Role を使い分る必要がある場合は ApolloProvider
ごと切り分けられるとよさそうです。
まとめ
Hasura の Role と Permission は最初はとっつきづらいですが、理解さえしてしまえば通常のバックエンドを実装するよりも早いのはもちろんのこと、使いこなせれば堅牢な API が作れる可能性があります。
バックエンドをコードとして実装するよりもできることは限られてくる分、データベースの設計がより重要になってきます。
というところで最後に宣伝となりますが、 SaaS のデータベース設計ができるエンジニアを募集しております。
すみません、しばらくこんな感じで最後にぶっ込んでいくと思います。