Hasura GraphQLとAuth0で認証をする

## はじめに
Hasuraを使っていて認証をするときにどうすればいいんだろうってなったので自分なりに記事にしておきます。
## Hasuraとは
HasuraはPostgres上でGraphQLを簡単に扱うことのできるGraphQL Engineです。
コンソール上でスキーマの定義などが全て行えてすごい楽。
https://hasura.io/

## Hasuraを準備する
### Hasuraをデプロイ
- [Hasura Doc](https://docs.hasura.io/1.0/graphql/manual/getting-started/heroku-simple.html)を開き、`Deploy to Heroku`をクリックします  
![スクリーンショット 2019-07-20 16.25.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/6ce09bca-9602-faf9-3ee9-e4beb3eb2022.png)
- 次にHerokuの画面が出てくるので適当なアプリ名を決めてデプロイ
![スクリーンショット 2019-07-20 16.30.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/ad6a5ba3-94b9-54d9-7734-2d098f52e718.png)  

### テーブルを定義
デプロイが完了したら`Manage app`からアプリの管理画面に飛ぶので右上の`Open app`からHasuraのコンソールを開きます。
コンソールを開いたらヘッダーにある`DATA`をクリックしてスキーマ定義のページに飛びます。
<img width="1440" alt="スクリーンショット 2019-07-20 16.39.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/10a04722-d441-bfbc-96f7-f53026f8d4ae.png">
次に`Create Table`をクリックしてテーブルを定義をします。今回はUserがArticleを作成するときのことを想定します。Userはidとnameを文字列で持っていて、主キーはidです。
<img width="1007" alt="スクリーンショット 2019-07-20 16.44.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/cf385f30-0c1a-9889-cfa9-59dc7506614b.png">
次にArticleはidとtitleとbody、そして作成したuserのidを持ち、主キーはidです。
<img width="1020" alt="スクリーンショット 2019-07-20 16.56.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/1ab6e173-b9ec-f07f-c427-a4e7a64ec904.png">
また、ArticleとUserには親子関係が発生するので `Foreign Key`を設定します。
<img width="823" alt="スクリーンショット 2019-07-20 16.58.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/b6710d87-6478-54fa-fd76-8fe3bafa606f.png">

### Roleの作成
先ほど設定したTablesに対してRoleを作成し権限を付与します。
ここではすでにadminというRoleが作成されているのでuserとanonymousというRoleを追加で作成します。
#### anonymousの作成
anonymousは記事を見ることしかできないのでarticleのデータを一部を見ることに対してのみ権限を与えます。
左のTablesにあるarticleを開き、Permissionsタブを選択します。
そしてRoleのところに`anonymous`と入力し、selectのところのペンマークをクリックして下の画像のような設定を行います。
下の設定では、全ての列に対して中身を見ることを許すので`Allow role anonymous to select rows:`の部分は`Without any checks`を選択します。
また、articleの中身は公開してもよいので`Toggle All`を選択します。
最後のaggregationに関しても今回は公開しても大丈夫なのでチェックをつけています。
![スクリーンショット 2019-07-20 17.32.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/74a0ae75-3220-13a8-02b3-5b7bfb7fabbb.png)

#### userの作成
anonymousと同じようにuserのRoleも作成していきます。
userは自分の書いた記事は編集をすることができるのでarticleのinsertとupdate、deleteに対しては以下の設定をします。
<img width="1435" alt="スクリーンショット 2019-07-20 17.34.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/5aae9865-2794-4061-ba58-694d1f0c1ef7.png">


```json:With-custom-check
{
  "user_id":{
    "_eq": "X-Hasura-User-Id"
  }
}
```
この設定はarticleのuser_idが`X-Hasura-User-Id`と同一のときにpermissionを与えるというものです。selectに関してはanonymousのときと同じ設定で大丈夫です。
次にuserというRoleは自分自身の情報も編集できるので同じような設定をuserテーブルにも行います。


### 環境変数の設定
HasuraをデプロイしているHerokuのコンソールに移動し、`Settings`を開きます.  次に`Config Vars`の`Reveal Config Vars`を押し、環境変数の設定を行います。
ここでは、`HASURA_GRAPHQL_ADMIN_SECRET`と`HASURA_GRAPHQL_UNAUTHORIZED_ROLE`という2つの環境変数を設定します.
#### HASURA_GRAPHQL_ADMIN_SECRET
あとで出てくるJWTモードを有効するために必要となります。 またこれを設定するとHasuraのコンソールにログインするときに入力が求められるようになります。  
#### HASURA_GRAPHQL_UNAUTHORIZED_ROLE
エンドポイントにクエリを投げるときに、認証が行われていない場合はここに設定されたRoleの権限で許されていることが実行することができます。
<img width="1440" alt="スクリーンショット 2019-07-20 18.02.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/cd3a9ec3-135d-7cf4-a221-14145dcafe55.png">
今回は先ほど作った`anonymous`というRoleを`HASURA_GRAPHQL_UNAUTHORIZED_ROLE`に設定しました。
また、`HASURA_GRAPHQL_ADMIN_SECRET`は今回`xxxxxxxx`にしていますがこれが漏れたら終わりなので出来る限りセキュアなものにしてください。

## Auth0の準備
### アプリケーションの作成
[Auth0のダッシュボード](https://manage.auth0.com/dashboard/)に移動し、`CREATE APPLICATION`から新規アプリケーションを作成します。
アプリケーションタイプに関しては自分の作りたいアプリにあったものを選択
<img width="801" alt="スクリーンショット 2019-07-20 19.02.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/23249d67-cc26-8ca8-784f-0d3218bb7bc2.png">

### Roleの作成
Auth0のサイドバーから`Users & Roles`をクリックし、`Roles`を選択する. その後`CREATE ROLE`からRoleを作成していきます。今回は`admin`と`user`というRoleを作成します。
<img width="1129" alt="スクリーンショット 2019-07-20 19.30.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/538c31ed-820d-74ac-a23d-407fd5210fbd.png">

### Ruleの作成
今回はuserを作成するためのRule(insert-user)と認証を行いRoleを返すためのRule(hasura-jwt-claim)を作成します。
RuleはサイドバーでRulesをクリックし、`CREATE RULE`から作成します。templateは`empty rule`を選択します。
#### insert-user
このRuleではユーザ-がAuth0でログインまたはサインアップを行ったときに、Hasuraにクエリを投げてユーザ登録または更新を行います。
<img width="1120" alt="スクリーンショット 2019-07-20 19.40.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/1f9f7531-4c29-e8d6-1ffc-877547ea15ad.png">

```javascript:insert-user
function (user, context, callback) {
  const userId = user.user_id;
  const username = user.nickname;
  
  const admin_secret = "[YOUR_ADMIN_SECRET_KEY]";
  const url = "https://[YOUR_APP_NAME].herokuapp.com/v1/graphql";
  request.post({
      headers: {'content-type' : 'application/json', 'x-hasura-admin-secret': admin_secret},
      url:   url,
      body:    `{\"query\":\"mutation($userId: String!, $username: String) {\\n insert_user(\\n objects: [{ id: $userId, name: $username }]\\n on_conflict: {\\n constraint: user_pkey\\n update_columns: name\\n }\\n ) {\\n affected_rows\\n }\\n }\",\"variables\":{\"userId\":\"${userId}\",\"username\":\"${username}\"}}`
  }, function(error, response, body){
       console.log(body);
       callback(null, user, context);
  });
}
```

#### hasura-jwt-claim
このRuleではログインしたユーザーのRoleをJWTトークンに含めるようにしています。
また今回はRoleが設定されていなかった場合はuserのRoleを返すようにしています。
(JavaScriptに慣れていないので変なところがあったら教えてください)
(追記：5行目のrole.keys.lengthのところはkeysがない場合もあるそうです)
<img width="1102" alt="スクリーンショット 2019-07-20 19.55.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/e6fd9e6f-f498-528d-2542-bb4c94d6eb2d.png">

```javascript:hasura-jwt-claim
function (user, context, callback) {
  let role = ["user"];
  const namespace = "https://hasura.io/jwt/claims";
  if(context.authorization !== undefined && (context.authorization || {}).roles.keys.length){
    role = (context.authorization || {}).roles;
  }
  
  context.idToken[namespace] = 
    { 
      'x-hasura-default-role': role[0],
      'x-hasura-allowed-roles': role,
      'x-hasura-user-id': user.user_id
    };
  callback(null, user, context);
}
```

### その他の設定
サイドバーの`Applications`を開いて、今回作成したアプリケーションを選択します。 `Settings`のところの`Allowed Callback URLs`と`Allowed Web Origins`を設定していきます. 今回はサンプルなのでどちらも以下のように設定します。
<img width="1086" alt="スクリーンショット 2019-07-20 20.00.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/05ee6ed5-909c-6b2b-dcde-e1566fe6d509.png">

## HasuraとAuth0の連携
[https://hasura.io/jwt-config](https://hasura.io/jwt-config)に移動します。
そうすると以下のようなページが出てくるので`Select Provider`に`Auth0`を選択し、`Enter Auth0 Domain Name`に先ほど作った自分のAuth0のアプリケーションのURLを入力します。 アプリケーションのURLは先ほど`Allwed Web Origins`などを設定したところの上の方の`Domain`に書いてあります。
入力が終わったら`GENERATE CONFIG`をクリックしてJWT Configを作成します。
<img width="618" alt="スクリーンショット 2019-07-20 20.07.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/6f8ad6fd-ca0f-fd03-a142-10743a9d685a.png">
作成が終わったらHerokuのコンソールから`HASURA_GRAPHQL_JWT_SECRET`という環境変数を作成します。 環境変数には先ほど作成したJWT Configをセットしてください。
<img width="1245" alt="スクリーンショット 2019-07-20 20.12.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/ad445ccc-a22c-0fad-13b8-ecf5f91692d4.png">

これでHasuraとAuth0の連携は終了です。

## Auth0 Tokenのテスト
1. [https://[YOUR_DOMAIN].auth0.com/login?client=[YOUR_CLIENT_ID]&protocol=oauth2&response_type=token%20id_token&redirect_uri=[YOUR_CALLBACK_URL]&scope=openid%20profile](https://[YOUR_DOMAIN].auth0.com/login?client=[YOUR_CLIENT_ID]&protocol=oauth2&response_type=token%20id_token&redirect_uri=[YOUR_CALLBACK_URL]&scope=openid%20profile)
の**[YOUR_DOMAIN]**を自分のアプリの`ドメイン名`、**[YOUR_CLIENT_ID]**をAuth0のApplicationのSettingsにある`Client ID`、**[YOUR_CALLBACK_URL]**のところを先ほどAuth0で設定した`Callback URL`に変更してリンクにアクセスしてください。
2. ログインに成功したら以下のようなページに遷移されます。
![スクリーンショット 2019-07-20 20.30.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254486/dfe6777c-e46e-d1f4-670a-aa898bdb42ac.png)
ここのURLのid_tokenの部分がJWTです
3. JWTは[jwt.io](https://jwt.io)でデバッグをすることができます。
4. このテストの部分は[Test with Auth0 Token](https://learn.hasura.io/graphql/hasura/authentication/5-test-with-headers)にあるので参考にしてください。

## さいごに
この半年間はずっとHasuraを使ったアプリケーション開発を行っているのですが、とても高速にアプリケーションを作成出来るのでとてもいい感じです。 また日本語であまりHasuraに関する記事などを見たことがなかったのでこれが皆さんの参考になると嬉しいです。