あ、しんのきです

趣味とか技術系とか

Hasura の Role と Permission について理解する

しんのきです。

Hasura の Tips 的なやつをバンバン書いていきたいのですが、とりまとめがないのでしばらく個人ブログに書きたいことを書いていきます。

概要

例としてログイン済のユーザーが記事を投稿でき、ログインしていないユーザーでも記事を閲覧できるようなサービスを考えます。

テーブル設計は単純に以下のようになります。

f:id:konoki_nannoki:20200728083149p:plain

テーブル設計が済んだところで、 Hasura ではこのようなサービスを Role と Permission を設定することによって直感的に表現できます。

Role と Permission を用いて表現すると、

  • user および anonymous Role は user および post を自由に select できる
  • user Role は user.id もしくは post.user_id が自分のユーザーIDと一致する場合に insert / update / delete できる

というように表現されます。 Role の名前は自由に決めることができますが、今回のような場合 useranonymous というように名付けると分かりやすいです。

例えば post の Permission は以下のように設定できます。

f:id:konoki_nannoki:20200728081648p:plain

✔ になっているのがフルアクセス許可、 ✘ はアクセス拒否、フィルターっぽいアイコンになっているのが条件付きのアクセス許可です。

細かくは割愛いたしますが 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 の subx-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-secretx-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 そのものがその場で消えます。

f:id:konoki_nannoki:20200728004651p:plain

また Admin アクセスでは 任意の Session Variables を渡すことができるので、 x-hasura-role: userx-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 のデータベース設計ができるエンジニアを募集しております。

www.wantedly.com

すみません、しばらくこんな感じで最後にぶっ込んでいくと思います。