[Advent Calendar 2021][Day13]HasuraGraphQLの認可機能を使って権限を制御する

undefined

はじめに

Advent Calendar 2021のDay13。 HasuraGraphQLの認可機能を使って権限を制御していきます。 すでにanonymousで権限の設定は行っていますが、認証されたユーザーのみがInsertできるようにするなど、FirebaseAuthenticationとHasuraを組み合わせて実現していきます。

Firebase AuthenticationとHasura GraphQL

Firebase AuthenticationとHasura GraphQLの連携に関する記事は、Hasura公式でもありますし、Qiitaなどでも沢山の記事があがっています。

参考

https://hasura.io/blog/authentication-and-authorization-using-hasura-and-firebase/

JWTについて詳しい記事

参考

https://hasura.io/docs/latest/graphql/core/auth/authentication/jwt.html

今回は、Nuxt3+Hasura+FirebaseAuthenticationの組み合わせですが、基本的な考え方は前述の記事の内容と同じです。 少し試してわかったのは、Nuxt3だとFirebase Realtime Databaseではバグがあるようできちんと動きませんでした。 なので、Firestoreを使うことにします。

docker-composeを修正する

まずはローカルで動作させるようにするので、docker-composeを修正します。 HASURA_GRAPHQL_JWT_SECRETを設定して、HasuraのJWT認証を有効にする。

  • docker-compose.yml
      HASURA_GRAPHQL_DEV_MODE: "true"      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log      ## uncomment next line to set an admin secret+      HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey+      HASURA_GRAPHQL_JWT_SECRET: ${HASURA_GRAPHQL_JWT_SECRET}+      HASURA_GRAPHQL_UNAUTHORIZED_ROLE: anonymousvolumes:  db_data: null

.envを追加して、docker-composeが読み込むよう変数を設定します。

  • .env
HASURA_GRAPHQL_JWT_SECRET='{"type":"RS256","jwk_url": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com", "audience": "<project_id>", "issuer": "https://securetoken.google.com/<project_id>"}'

<project_id>の部分はプロジェクトIDに置き換えます。

codegenにAdminSecretを設定する

上述でADMIN_SECRETを設定したので、codegenでも生成できるようシークレットを設定しておきます。

version: 3endpoint: http://localhost:8080+admin_secret: myadminsecretkeymetadata_directory: metadataactions:  kind: synchronous

hasuraのuser権限を設定する

権限については、userというroleを追加します。 userは、insertができるようになります。また自分がinsertしたレコードに対しては、updateとdeleteができるようになります。 各テーブルには、uidというカラムがあります。 insertされたときに、そのレコードのuidをHasura側で自動的に設定します。 updatedeleteは、そのレコードのuidが自分のものであることを確認してから実行します。

deleteも同様にuidを確認するよう修正します。

設定が完了したら、metadataをexportしておきます。

cd hasurahasura metadata export

カスタムクレイムを設定する

userが作成されたイベントをcloud functionsで受け取り、カスタムクレイムを設定します。 firebase cliをインストールして初期化します。firestoreを利用します。

npm install -g firebase-tools
mkdir firebase
cd firebase
firebase init
  • firebase/functions/index.js

Firestoreで生成されたユーザーのデータを設定します。フロント側では、該当データをリッスンしてカスタムクレイムを読み出すよう対応します。

const functions = require("firebase-functions");const admin = require("firebase-admin");const {getFirestore, doc, setDoc} = require( "firebase/firestore");admin.initializeApp(functions.config().firebase);const db = getFirestore();// On sign up.exports.processSignUp = functions.auth.user().onCreate((user) => {  const customClaims = {    "https://hasura.io/jwt/claims": {      "x-hasura-default-role": "user",      "x-hasura-allowed-roles": ["user"],      "x-hasura-user-id": user.uid,    },  };  return admin      .auth()      .setCustomUserClaims(user.uid, customClaims)      .then(() => {        setDoc(doc(db, "users", user.uid), {          refreshTime: new Date().getTime(),        });      })      .catch((error) => {        console.log(error);      });});

firestoreのルールを設定する

firebaseコンソールからFirestoreのルールを設定します。/users/{userId}に対して認証されたユーザのみがアクセスできるようにします。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

firebase functionをデプロイする

firebase deploy --only functions

これは、リージョンやメモリなど細かく設定しておいたほうが良いですが、一旦はこれでよいです。

hasuraカスタムクレイムを取得する

結構差分が出ちゃったので、画面ショットで。 やっていることは、

  • firestoreのimport,インスタンス取得
  • cloud functionsでカスタムクレイムが設定されてから、getIdTokenでカスタムクレイムを取得する
  • 各所でfirestoreのunsubscribe

まとめ

これらを行うことによって、以下の仕組みが整いました。

  • FirebaseAuthenticationで認証
  • Hasuraでロールによる権限制御

次回は、カレンダー登録機能を作っていきます。