はじめに
この記事は公式チュートリアルを参考に認証機能を実装する記事です。公式チュートリアルもかなり充実しているのですが、Auth0周りはつまづきポイントが多かったので、これの副読書として、認証機能周りに着目して解説していきたいと思います!
Hasuraとは?
HasuraとはHasura社の提供するOSSです。たぶんBaaS(Backend As A Service)の1つです。立ち位置としてはDBのラッパー?という感じで、DBにHasuraを接続するだけでGraphQLの書き方でCRUD操作ができます。その他にRemote SchemeやHasura Actionsなど、他APIと連携するための仕組みが提供されています。
Auth0とは?
Auth0とはAuth0社の提供する認証プラットフォームサービスです。いわゆるIDaaS(Identity as a Service)の1つです。カスタマイズ性が高く、セキュリティレベルの高さが特徴で、認証時にHasuraに認証したユーザーの情報をクエリとして投げるというように任意の処理を実行することができます。
本編
早速認証機能を実装していく!
下準備
認証機能の実装方法の説明に集中するためにHasuraの基本的な操作や権限の詳細については省略しています。ここの部分は公式チュートリアルを触ってみるとわかりやすいです。
認証機能を実装するまえに、Hasuraのプロジェクトを作成し、認証したユーザーのデータを格納するusersテーブルを作成します。このリンクをクリックして「Get Started in 30 seconds」をクリックし、プロジェクトを作成してください。コンソール画面を開けたらData>Create New Databaseをクリックしてください。そうすると以下のような画面になります。
公式チュートリアルではHerokuを使用していますが、有料化に伴い、無料枠のあるNeonというBaaSのサービスに置き換えられました。「Connect Neon Database」をクリックし、HasuraにPostgreSQLに接続します。しばらく時間がかかります。
DBが接続に成功したら下記のような画面に勝手に変わります。そうしたら、「Create Table」をクリックしてください。
早速、Auth0で認証したユーザーのデータを書き込むためのテーブルを作成します。下記のようにカラムを設定してください。idはAuth0の方で認証時に生成されるのですが、これが文字列なのでIntegerなどにしないように気をつけてくだだい。
設定できたら、下の方にスクロールして「Add Table」をクリックしてください。
次にテーブルを閲覧できるユーザーを制限します。Permissionsタブをクリックしてください。その後、userロールを追加し、select列のところをクリックします。
Row select permissions>With custom checkに以下の内容を入力します。
{"id": {"_eq": "X-Hasura-User-Id"}}
上記の設定により、認証した際に生成されるJWTに埋め込まれているのX-Hasura-User-Idとidが一致しない行のデータは表示されなくなります。JWTについては下記の記事を参考にしてください。
列レベルの権限を以下のように設定します。
以上で下準備完了です!
Auth0アプリを作成する
ここからAuth0の設定を進めていきます。
Auth0ダッシューボードにアクセスします。
左上の「dev-*******」みたいな名前の場所をクリックして「Create Tenant」をクリックします。
「Tenant Domain」を適当に設定します。ここでは「hasura-de-auth0-tsukauyo」とします。
テナントドメインを入力したら、「Create」をクリックします。
次にアプリケーションを作成します。左下の「>>」をクリックして、「Applications」のプルダウンを開き、「Applications」をクリックします。
「Create Application」をクリックします。
名前を「My App」とし、「Single Page Web Application」を選択し、「Create」をクリックしてください。
これでアプリケーションの作成は完了です。次にAPIを作成します。先程と同様に「>>」をクリックし、「Applications>APIs」をクリックしてください。もしくはここをクリックしてください。
「Create API」をクリックし、Nameに「hasura」とIdentifierに「https://hasura.io/learn」を入力します。Identifierはなんでもいいです。入
力したら「Create」をクリックしてください。
カスタムJWTクレームのルール
クライアントからのクエリをHasuraで受け取ったとき、ユーザーのIDやロールなどの情報が埋め込まれたJWTが必要になります。ここではJWTにどのようなデータを埋め込むかを設定します。
左のメニューから「Auth Pipeline>Rules」を選択します。その後、「Create」をクリックし、「Empty rule」をクリックします。Nameに「hasura-jwt-claims」と入力し、下記の内容を書き込みます。
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);
}
JWTに「x-hasura-default-role」「x-hasura-allowed-roles」「x-hasura-user-id」を埋め込むように設定しています。
HasuraをAuth0に接続する
ここからAuth0用の公開キーを生成します。
「Select Provider」に「Auth0」、「Enter Auth0 Domain Name」にテナントドメインを入力してください。そして、「GENERATE CONFIG」をクリックすると下記のように公開キーが表示されます。
公開キーをHasuraの環境変数に登録します。Hasuraのコンソール画面の右上にある「SETTINGS」のとなりのボタンをクリックします。
「Env vars」をクリックし、「New Env Var」をクリックします。その後、HASURA_GRAPHQL_JWT_SECRET
という変数を作り、先程生成した公開キーを入力します。
入力できたら「Add」を押します。
Rulesでユーザーを同期する
Auth0でユーザー認証が行われた際に認証したユーザーの情報をHasuraのuserテーブルに登録する必要があります。この処理を書いていきます。
Auth Pipeline>Rules
をクリックし、「Crate」ボタンをクリックしてください。「Empty rule」を選択し、下記のコードを入力していきます。
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を入力してください。
入力できたらSave Changesで保存してください。この段階で「Save And Try」をクリックすると下記のようなユーザーが登録されていると思います。
うまく行かない場合は「Save And Install Real-time Logs」をクリックし、デバッグ用のAuth0拡張機能を入れてください。「Installed Extentions」にある「Real-time Webtask Logs」をクリックし、Auth0との連携を有効にした上で再度「Save And Try」をクリックしてみてください。
失敗している場合は失敗理由が表示されていると思うのでそれを参考にコードやこれまでの設定を見直してみてください。
例として、admin_secret
が間違っている場合は下記のメッセージが得られます。
Auth0トークンで試す
実際にAuth0で認証し、JWTを受け取り、Hasuraにクエリを投げてみます。
はじめにデバッグ用のツールである「Auth0 Authentication API Debugger」を「Extentions」をクリックしてインストールします。
インストール後、「Extentions>Installed Extentions」から「Auth0 Authentication API Debugger」をクリックし、Auth0との連携を許可します。すると以下のような画面が表示されます。
少し下にスクロールすると「Callback URL」という項目があると思います。これをコピーして「Applications>Applications>My App>Settings」の「Allowed Callback URLs」にペーストしてください。
その後、画面を下までスクロールし、「Save Changes」をクリックしてください。
次に拡張機能の画面に戻り、「OAuth2 / OIDC」タブに切り替え、下の方にスクロールし、AudienceにApplication>APIsで設定したIdentifierの値を入力して、右のトグルスイッチをオンにしてください。
その後、「OAuth2 / OIDC」タブの「OAUTH2 / OIDC LOGIN」ボタンを押してください。下記のような画面が出て来ると思います。
認証し下記のような画面に遷移すれば成功です。
下記のような画面に遷移する場合はAllowed Callback URLsが保存できていない可能性があります。
実際にクエリを叩いてみる
「Hash Fragments」の「access_token」の値をコピーしてください。それをHasuraのコンソール画面のヘッダの部分に新しく「Authorization」という項目を追加し、「Bearer $access_tokenの値」としてください。
この状態で下記のクエリを実行してください。
query {
users {
id
name
last_seen
}
}
これで自身のデータだけを取得することができれば認証成功です!お疲れさまでした!
終わりに
筆者はAuth0による認証機能の公式チュートリアルでつまづきまくっていたのでこの記事が誰かの参考になればと思います。Hasura自体はGraphQLやAPIの面倒な部分を代わりにやってくれる熱いサービスだと思いますので、この記事でHasuraに興味を持っていただけると幸いです。
普段はいんでぃーはっかーの集いというコミュニティで活動しています。質問がある方や個人開発仲間を探している方はぜひご参加ください!