Hasura+Auth0で認証機能を実装する記事

# はじめに

この記事は[公式チュートリアル](https://hasura.io/learn/ja/graphql/hasura/introduction/)を参考に認証機能を実装する記事です。公式チュートリアルもかなり充実しているのですが、Auth0周りはつまづきポイントが多かったので、これの副読書として、認証機能周りに着目して解説していきたいと思います!

# Hasuraとは?

HasuraとはHasura社の提供するOSSです。たぶんBaaS(Backend As A Service)の1つです。立ち位置としてはDBのラッパー?という感じで、DBにHasuraを接続するだけでGraphQLの書き方でCRUD操作ができます。その他にRemote SchemeやHasura Actionsなど、他APIと連携するための仕組みが提供されています。

# Auth0とは?

[Auth0](https://auth0.com/jp?utm_content=japanjpbrandauth0-generic-&utm_source=google&utm_campaign=apac_japan_jpn_all_ciam-all_dg-ao_auth0_search_google_text_kw_utm2&utm_medium=cpc&utm_id=aNK4z0000004IZLGA2&utm_term=auth0-c&gclid=Cj0KCQjw8e-gBhD0ARIsAJiDsaVZgICsuwRHC5DpCLpyoS0Lacr8DQEAKynBxz3CkZJmiC5-Z5Re9fgaAtHPEALw_wcB)とはAuth0社の提供する認証プラットフォームサービスです。いわゆるIDaaS(Identity as a Service)の1つです。カスタマイズ性が高く、セキュリティレベルの高さが特徴で、認証時にHasuraに認証したユーザーの情報をクエリとして投げるというように任意の処理を実行することができます。

# 本編

早速認証機能を実装していく!

## 下準備

認証機能の実装方法の説明に集中するためにHasuraの基本的な操作や権限の詳細については省略しています。ここの部分は公式チュートリアルを触ってみるとわかりやすいです。

https://hasura.io/learn/ja/graphql/hasura/introduction/

認証機能を実装するまえに、Hasuraのプロジェクトを作成し、認証したユーザーのデータを格納するusersテーブルを作成します。このリンクをクリックして「Get Started in 30 seconds」をクリックし、プロジェクトを作成してください。コンソール画面を開けたらData>Create New Databaseをクリックしてください。そうすると以下のような画面になります。

![スクリーンショット 2023-03-23 22.38.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/bc78a823-0e49-eedc-f8ea-3c14b8cdeb9d.png)

公式チュートリアルでは[Heroku](https://www.heroku.com/)を使用していますが、有料化に伴い、無料枠のある[Neon](https://neon.tech/)というBaaSのサービスに置き換えられました。「Connect Neon Database」をクリックし、HasuraにPostgreSQLに接続します。しばらく時間がかかります。

DBが接続に成功したら下記のような画面に勝手に変わります。そうしたら、「Create Table」をクリックしてください。

![スクリーンショット 2023-03-23 22.42.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/9bf1dc7e-98c8-d1a0-b627-7d74a19f24bd.png)

早速、Auth0で認証したユーザーのデータを書き込むためのテーブルを作成します。下記のようにカラムを設定してください。idはAuth0の方で認証時に生成されるのですが、これが文字列なのでIntegerなどにしないように気をつけてくだだい。

設定できたら、下の方にスクロールして「Add Table」をクリックしてください。

![スクリーンショット 2023-03-23 22.50.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/b8b34cf7-6a0e-5f28-212c-f48b61019c9f.png)

次にテーブルを閲覧できるユーザーを制限します。Permissionsタブをクリックしてください。その後、userロールを追加し、select列のところをクリックします。

![スクリーンショット 2023-03-23 22.53.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/24721bac-e56d-a749-4bc6-df0532536694.png)

Row select permissions>With custom checkに以下の内容を入力します。

```
{"id": {"_eq": "X-Hasura-User-Id"}}
```

![スクリーンショット 2023-03-23 22.55.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/66f2e511-6221-657d-0a59-9a5ee64e5d70.png)

上記の設定により、認証した際に生成されるJWTに埋め込まれているのX-Hasura-User-Idとidが一致しない行のデータは表示されなくなります。JWTについては下記の記事を参考にしてください。

https://qiita.com/ichi_zamurai/items/8f0887c7bfb4c206b795

列レベルの権限を以下のように設定します。

![スクリーンショット 2023-03-23 23.00.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/fee35a30-ad53-2e0a-f226-ecb825214f3a.png)

以上で下準備完了です!

## Auth0アプリを作成する

https://hasura.io/learn/ja/graphql/hasura/authentication/1-create-auth0-app/

ここからAuth0の設定を進めていきます。
[Auth0ダッシューボード](https://manage.auth0.com/)にアクセスします。

左上の「dev-*******」みたいな名前の場所をクリックして「Create Tenant」をクリックします。

![スクリーンショット 2023-03-17 9.15.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/259d719a-86b2-fab9-a458-ff22317b4296.png)

「Tenant Domain」を適当に設定します。ここでは「hasura-de-auth0-tsukauyo」とします。

![スクリーンショット 2023-03-17 9.17.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/ddc2b6f0-75e7-ae64-4a89-c3141a1dee15.png)

テナントドメインを入力したら、「Create」をクリックします。

次にアプリケーションを作成します。左下の「>>」をクリックして、「Applications」のプルダウンを開き、「Applications」をクリックします。

![スクリーンショット 2023-03-17 9.20.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/b3067ab6-aab6-6f9f-cf25-df166a26535a.png)

「Create Application」をクリックします。

![スクリーンショット 2023-03-17 9.22.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/e7c6f813-5b5c-f804-e520-96b3160c4439.png)

名前を「My App」とし、「Single Page Web Application」を選択し、「Create」をクリックしてください。

![スクリーンショット 2023-03-17 9.26.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/4b1ca956-8a94-acfe-bd44-bb5eb41de4c8.png)

これでアプリケーションの作成は完了です。次にAPIを作成します。先程と同様に「>>」をクリックし、「Applications>APIs」をクリックしてください。もしくは[ここ](https://manage.auth0.com/dashboard/us/hasura-de-auth0-tsukauyo/apis0)をクリックしてください。

「Create API」をクリックし、Nameに「hasura」とIdentifierに「https://hasura.io/learn」を入力します。Identifierはなんでもいいです。入

![スクリーンショット 2023-03-17 9.34.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/803e06ce-bd64-468a-1160-2cb727ccb87f.png)

力したら「Create」をクリックしてください。

## カスタムJWTクレームのルール

https://hasura.io/learn/ja/graphql/hasura/authentication/2-custom-jwt-claims-rule/

クライアントからのクエリをHasuraで受け取ったとき、ユーザーのIDやロールなどの情報が埋め込まれたJWTが必要になります。ここではJWTにどのようなデータを埋め込むかを設定します。

左のメニューから「Auth Pipeline>Rules」を選択します。その後、「Create」をクリックし、「Empty rule」をクリックします。Nameに「hasura-jwt-claims」と入力し、下記の内容を書き込みます。

```javascript
function (user, context, callback) {
  const namespace = "https://hasura.io/jwt/claims";
  context.accessToken[namespace] =
    {
      'x-hasura-default-role': 'user',
      // do some custom logic to decide allowed roles
      'x-hasura-allowed-roles': ['user'],
      'x-hasura-user-id': user.user_id
    };
  callback(null, user, context);
}
```

![スクリーンショット 2023-03-17 9.47.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/c8814c55-31e5-edd5-c8f1-c06b0ba1679a.png)

JWTに「x-hasura-default-role」「x-hasura-allowed-roles」「x-hasura-user-id」を埋め込むように設定しています。

## HasuraをAuth0に接続する

https://hasura.io/learn/ja/graphql/hasura/authentication/3-setup-env-vars-hasura/

[ここ](https://hasura.io/jwt-config/)からAuth0用の公開キーを生成します。
「Select Provider」に「Auth0」、「Enter Auth0 Domain Name」にテナントドメインを入力してください。そして、「GENERATE CONFIG」をクリックすると下記のように公開キーが表示されます。

![スクリーンショット 2023-03-23 18.54.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/ca90f216-190c-bbe4-5da9-f94161938611.png)

公開キーをHasuraの環境変数に登録します。Hasuraのコンソール画面の右上にある「SETTINGS」のとなりのボタンをクリックします。

![スクリーンショット 2023-03-23 18.51.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/97b4722d-6151-8a25-05e7-b7988f4fe08c.png)

「Env vars」をクリックし、「New Env Var」をクリックします。その後、`HASURA_GRAPHQL_JWT_SECRET`という変数を作り、先程生成した公開キーを入力します。

![スクリーンショット 2023-03-23 18.55.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/063c1432-3835-2ca4-ff04-6feb2e03e4a3.png)

入力できたら「Add」を押します。

## Rulesでユーザーを同期する

https://hasura.io/learn/ja/graphql/hasura/authentication/4-user-sync-rule/

Auth0でユーザー認証が行われた際に認証したユーザーの情報をHasuraのuserテーブルに登録する必要があります。この処理を書いていきます。

`Auth Pipeline>Rules`をクリックし、「Crate」ボタンをクリックしてください。「Empty rule」を選択し、下記のコードを入力していきます。

```javascript
function (user, context, callback) {
  const userId = user.user_id;
  const nickname = user.nickname;

  const admin_secret = "xxxxxx";
  const url = "https://ready-panda-91.hasura.app/v1/graphql";
  const query = `mutation($userId: String!, $nickname: String) {
    insert_users(objects: [{
      id: $userId, name: $nickname, last_seen: "now()"
    }], on_conflict: {constraint: users_pkey, update_columns: [last_seen, name]}
    ) {
      affected_rows
    }
  }`;

  const variables = { "userId": userId, "nickname": nickname };

  request.post({
      url: url,
      headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': admin_secret},
      body: JSON.stringify({
        query: query,
        variables: variables
      })
  }, function(error, response, body){
       console.log(body);
       callback(null, user, context);
  });
}
```

このとき`admin_secret`と`url`の値を適した値に変更してください。
`admin_secret`にはHasura側の環境変数`HASURA_GRAPHQL_ADMIN_SECRET`の値を入力してください。

`url`にはコンソール上のURLを入力してください。

![スクリーンショット 2023-03-23 19.15.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/517c4742-ca88-57d8-08aa-e07eabeaa9f4.png)

入力できたらSave Changesで保存してください。この段階で「Save And Try」をクリックすると下記のようなユーザーが登録されていると思います。

![スクリーンショット 2023-03-23 23.34.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/818bf91d-38d9-01c4-bea2-ba74e160f86c.png)

うまく行かない場合は「Save And Install Real-time Logs」をクリックし、デバッグ用のAuth0拡張機能を入れてください。「Installed Extentions」にある「Real-time Webtask Logs」をクリックし、Auth0との連携を有効にした上で再度「Save And Try」をクリックしてみてください。

成功している場合は下記のような出力が得られます。
![スクリーンショット 2023-03-23 20.30.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/e0175ec9-ca29-1c3f-466f-ff2083cf3d26.png)

失敗している場合は失敗理由が表示されていると思うのでそれを参考にコードやこれまでの設定を見直してみてください。

例として、`admin_secret`が間違っている場合は下記のメッセージが得られます。

![スクリーンショット 2023-03-23 20.31.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/f2013492-f1b8-9a10-550c-53971cb4bb0b.png)

## Auth0トークンで試す

https://hasura.io/learn/ja/graphql/hasura/authentication/5-test-with-headers/

実際にAuth0で認証し、JWTを受け取り、Hasuraにクエリを投げてみます。
はじめにデバッグ用のツールである「Auth0 Authentication API Debugger」を「Extentions」をクリックしてインストールします。

![スクリーンショット 2023-03-23 19.39.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/2fe0e74b-8311-64a0-8915-68be4238460d.png)

インストール後、「Extentions>Installed Extentions」から「Auth0 Authentication API Debugger」をクリックし、Auth0との連携を許可します。すると以下のような画面が表示されます。

![スクリーンショット 2023-03-23 19.48.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/3485e15f-2f8e-3ec7-e5a8-862e54e109c8.png)

少し下にスクロールすると「Callback URL」という項目があると思います。これをコピーして「Applications>Applications>My App>Settings」の「Allowed Callback URLs」にペーストしてください。

![スクリーンショット 2023-03-23 19.51.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/48c1fa86-10a7-4e7a-e9f7-c2496ca7774f.png)

その後、画面を下までスクロールし、「Save Changes」をクリックしてください。

次に拡張機能の画面に戻り、「OAuth2 / OIDC」タブに切り替え、下の方にスクロールし、AudienceにApplication>APIsで設定したIdentifierの値を入力して、右のトグルスイッチをオンにしてください。

![スクリーンショット 2023-03-23 19.53.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/575a144c-7acf-dbd2-0aa0-df32bfc5492d.png)
![スクリーンショット 2023-03-23 19.55.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/be7b9d58-71fa-b9e9-a07d-fc1ad953d254.png)

その後、「OAuth2 / OIDC」タブの「OAUTH2 / OIDC LOGIN」ボタンを押してください。下記のような画面が出て来ると思います。

![スクリーンショット 2023-03-23 20.07.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/01318afd-3e62-6f1f-f9ef-38262c2ce510.png)

認証し下記のような画面に遷移すれば成功です。

![スクリーンショット 2023-03-23 20.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/d1440fd8-2fda-0f3d-f48b-136751695d53.png)

下記のような画面に遷移する場合はAllowed Callback URLsが保存できていない可能性があります。

![スクリーンショット 2023-03-23 20.33.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/5f739aaf-d4c4-3caf-922b-b32ec8f86c56.png)

## 実際にクエリを叩いてみる

「Hash Fragments」の「access_token」の値をコピーしてください。それをHasuraのコンソール画面のヘッダの部分に新しく「Authorization」という項目を追加し、「Bearer $access_tokenの値」としてください。

![スクリーンショット 2023-03-24 0.15.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/2ae7d175-5325-f5aa-ed32-fb038468df89.png)

この状態で下記のクエリを実行してください。

```graphql
query {
    users {
        id
        name
        last_seen
    }
}
```

これで自身のデータだけを取得することができれば認証成功です!お疲れさまでした!

![スクリーンショット 2023-03-24 0.20.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2063050/a95f211c-5eaf-9fe5-cf6d-5bd2d2a250e5.png)


# 終わりに

筆者はAuth0による認証機能の公式チュートリアルでつまづきまくっていたのでこの記事が誰かの参考になればと思います。Hasura自体はGraphQLやAPIの面倒な部分を代わりにやってくれる熱いサービスだと思いますので、この記事でHasuraに興味を持っていただけると幸いです。

普段は[いんでぃーはっかーの集い](https://indie-hackers.connpass.com/)というコミュニティで活動しています。質問がある方や個人開発仲間を探している方はぜひご参加ください!