Hasura GraphQLとAuth0で認証をする

はじめに

Hasuraを使っていて認証をするときにどうすればいいんだろうってなったので自分なりに記事にしておきます。

Hasuraとは

HasuraはPostgres上でGraphQLを簡単に扱うことのできるGraphQL Engineです。 コンソール上でスキーマの定義などが全て行えてすごい楽。 https://hasura.io/

Hasuraを準備する

Hasuraをデプロイ

テーブルを定義

デプロイが完了したらManage appからアプリの管理画面に飛ぶので右上のOpen appからHasuraのコンソールを開きます。 コンソールを開いたらヘッダーにあるDATAをクリックしてスキーマ定義のページに飛びます。 スクリーンショット 2019-07-20 16.39.17.png 次にCreate Tableをクリックしてテーブルを定義をします。今回はUserがArticleを作成するときのことを想定します。Userはidとnameを文字列で持っていて、主キーはidです。 スクリーンショット 2019-07-20 16.44.28.png 次にArticleはidとtitleとbody、そして作成したuserのidを持ち、主キーはidです。 スクリーンショット 2019-07-20 16.56.16.png また、ArticleとUserには親子関係が発生するので Foreign Keyを設定します。 スクリーンショット 2019-07-20 16.58.48.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

userの作成

anonymousと同じようにuserのRoleも作成していきます。 userは自分の書いた記事は編集をすることができるのでarticleのinsertとupdate、deleteに対しては以下の設定をします。 スクリーンショット 2019-07-20 17.34.29.png

{
  "user_id":{
    "_eq": "X-Hasura-User-Id"
  }
}

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

環境変数の設定

HasuraをデプロイしているHerokuのコンソールに移動し、Settingsを開きます. 次にConfig VarsReveal Config Varsを押し、環境変数の設定を行います。 ここでは、HASURA_GRAPHQL_ADMIN_SECRETHASURA_GRAPHQL_UNAUTHORIZED_ROLEという2つの環境変数を設定します.

HASURA_GRAPHQL_ADMIN_SECRET

あとで出てくるJWTモードを有効するために必要となります。 またこれを設定するとHasuraのコンソールにログインするときに入力が求められるようになります。

HASURA_GRAPHQL_UNAUTHORIZED_ROLE

エンドポイントにクエリを投げるときに、認証が行われていない場合はここに設定されたRoleの権限で許されていることが実行することができます。 スクリーンショット 2019-07-20 18.02.02.png 今回は先ほど作ったanonymousというRoleをHASURA_GRAPHQL_UNAUTHORIZED_ROLEに設定しました。 また、HASURA_GRAPHQL_ADMIN_SECRETは今回xxxxxxxxにしていますがこれが漏れたら終わりなので出来る限りセキュアなものにしてください。

Auth0の準備

アプリケーションの作成

Auth0のダッシュボードに移動し、CREATE APPLICATIONから新規アプリケーションを作成します。 アプリケーションタイプに関しては自分の作りたいアプリにあったものを選択 スクリーンショット 2019-07-20 19.02.41.png

Roleの作成

Auth0のサイドバーからUsers & Rolesをクリックし、Rolesを選択する. その後CREATE ROLEからRoleを作成していきます。今回はadminuserというRoleを作成します。 スクリーンショット 2019-07-20 19.30.22.png

Ruleの作成

今回はuserを作成するためのRule(insert-user)と認証を行いRoleを返すためのRule(hasura-jwt-claim)を作成します。 RuleはサイドバーでRulesをクリックし、CREATE RULEから作成します。templateはempty ruleを選択します。

insert-user

このRuleではユーザ-がAuth0でログインまたはサインアップを行ったときに、Hasuraにクエリを投げてユーザ登録または更新を行います。 スクリーンショット 2019-07-20 19.40.07.png

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がない場合もあるそうです) スクリーンショット 2019-07-20 19.55.11.png

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 URLsAllowed Web Originsを設定していきます. 今回はサンプルなのでどちらも以下のように設定します。 スクリーンショット 2019-07-20 20.00.20.png

HasuraとAuth0の連携

https://hasura.io/jwt-configに移動します。 そうすると以下のようなページが出てくるのでSelect ProviderAuth0を選択し、Enter Auth0 Domain Nameに先ほど作った自分のAuth0のアプリケーションのURLを入力します。 アプリケーションのURLは先ほどAllwed Web Originsなどを設定したところの上の方のDomainに書いてあります。 入力が終わったらGENERATE CONFIGをクリックしてJWT Configを作成します。 スクリーンショット 2019-07-20 20.07.59.png 作成が終わったらHerokuのコンソールからHASURA_GRAPHQL_JWT_SECRETという環境変数を作成します。 環境変数には先ほど作成したJWT Configをセットしてください。 スクリーンショット 2019-07-20 20.12.21.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 の**[YOUR_DOMAIN]**を自分のアプリのドメイン名、**[YOUR_CLIENT_ID]**をAuth0のApplicationのSettingsにあるClient ID、**[YOUR_CALLBACK_URL]**のところを先ほどAuth0で設定したCallback URLに変更してリンクにアクセスしてください。
  2. ログインに成功したら以下のようなページに遷移されます。 スクリーンショット 2019-07-20 20.30.36.png ここのURLのid_tokenの部分がJWTです
  3. JWTはjwt.ioでデバッグをすることができます。
  4. このテストの部分はTest with Auth0 Tokenにあるので参考にしてください。

さいごに

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