Hasuraとsupabaseを使って認証まで。~ supabaseにclaimsの設定は要らなかった ~


はじめに

Hasuraとsupabaseを使って認証までの方法を紹介します。
1部他の方の記事を貼って、そこを参照してもらってる部分があります。

HASURA_GRAPHQL_JWT_SECRETを設定する部分で少し詰まったので記事にまとめておきました。
特にHasura認証用のカスタムクレームの設定の部分です。
自分が詰まった部分の解決法の結論としては、claims_mapを使うでした。

技術

  • Hasura
  • supabase
  • Next.js(React)

supabaseの認証機能の作成

supabaseの認証機能を使えるようにしていきます。

OAuthプロバイダにGoogleを使い認証機能を使えるようにするまではこのドキュメントにすごく細かく丁寧に書かれているので、ここでは省略します。
https://supabase.com/docs/guides/auth/auth-google
※ 他のプロバイダについての記事も書かれています。
※ このドキュメントだけでいけます。

  const handleSignIn = async () => {
    await supabase.auth.signIn({ provider: "google" });
  };
  const session = supabase.auth.session();
  const user = supabase.auth.user();
  console.log(session?.access_token);
  console.log(user);

ここまで行い、handleSignInを実行することで、
よくみるGoogleのログイン画面が開かれログインすることができます。

トークンだったりユーザー情報の取得までできました。

認証されたユーザーはauth.usersテーブルに追加されていきます。
supabaseのSQLを開いて、以下のSQLを実行すると追加されていることが確認できます。

SELECT * FROM auth.users

追加と同時に、public.usersテーブルに追加していくと楽です。
公式ドキュメントでもそのようにしています。

そのため、一旦auth.usersから消しました。

DELETE FROM auth.users

同時に追加する様にするためには、以下のSQLをsupabaseのSQLを開いて実行します

-- user table
create table users (
  id uuid references auth.users not null primary key,
  full_name text,
  avatar_url text
);
alter table users enable row level security;
create policy "Can view own user data." on users for select using (auth.uid() = id);
create policy "Can update own user data." on users for update using (auth.uid() = id);

-- inserts a row into public.users
create function public.handle_new_user()
  returns trigger as $$
begin
  insert into public.users (id, full_name, avatar_url)
  values (new.id, new.raw_user_meta_data->>'full_name', new.raw_user_meta_data->>'avatar_url');
  return new;
end;
$$ language plpgsql security definer;
-- trigger the function every time a user is created
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();

処理としては
まず、public.usersテーブルを作成しています。
publicのusersテーブルにユーザーを作成するfunctionを作成し、
auth.usersにデータが追加されるたびに実行することで、
auth.usersと同じidを持ったデータをpublic.usersテーブルに追加しています。

alter tableとcreate policyはRow Level Securityの設定です。
ログインしたユーザー自身のデータしか取得と更新をできなくしています。

supabaseでの(Hasuraを通さない部分)認証はこれでできました。

Hasuraとsupabaseを接続する

この記事で解説されていました。

やってることとしては、
supabaseでDBのurlを確認して、Hasuraで入力する、です。

認可の設定、HasuraでJWT認証

このドキュメントに詳しく載っていますが、必要なことは以下です。

  • HASURA_GRAPHQL_JWT_SECRETを設定する
  • headerにtokenを載せる

HASURA_GRAPHQL_JWT_SECRETを設定する

Hasuraを開き、Env vars → New Env Varから追加します。

{
    "key": "1111a1a1-1aaa-1a11-111a-111a1a111111",
    "type": "HS256",
    "claims_map": {
        "x-hasura-user-id": {
            "path": "$.sub"
        },
        "x-hasura-default-role": "user",
        "x-hasura-allowed-roles": [
            "user"
        ]
    }
}

keyはsupabaseを開き、setting → API → JWT Secret で確認できます。
1111a1a1-1aaa-1a11-111a-111a1a111111の部分に確認したJWT Secretを設定します。

$.subはsupabaseのauth.userの識別idです。

※ FirebaseやAuth0の例では、Hasura認証用のカスタムクレームの設定などをしていたのですが、supabaseに同様のことをする方法がわからなかった(出来ないかもな)ので、claims_mapを使いました。

headerにtokenを載せる

今回はNext.jsにapollo clientを使っています。

import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { supabase } from "src/utils/libs/initSupabase";

const httpLink = createHttpLink({
  uri: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT,
});

const authLink = setContext((_, { headers }) => {
  const token = supabase.auth.session()?.access_token;
  return token
    ? { headers: { ...headers, authorization: `Bearer ${token}` } }
    : { headers };
});

export const apolloClient = new ApolloClient({
  link: typeof window === "undefined" ? httpLink : authLink.concat(httpLink),
  cache: new InMemoryCache(),
});

今回したいことは、headerにtokenを載せてるの部分なので、
supabase.auth.session()?.access_token;で取得したものをheaderに載せてることを理解していただければと思います。

テーブルに権限を設定をする

あとは、実際に権限を設定したいテーブルに、設定をしていくことで使えます。
Hasuraを開き、DATA → Permissionsを開き、設定していきます。

画像では、ログインしているuser自身のデータのみに、更新や検索ができる状態です。
user_idがX-Hasura-User-Idのデータのみと制限をつけています。

※ これはusersテーブルではない別のテーブルなのでuser_idになっています。
※ usersテーブルであれば、idになります。

これで完了です。
ログアウトしたら同じクエリでも、データが取得できなかったので成功です。
お疲れ様でした。

最後に

supabaseの方の公式ドキュメントの関連しそうな部分全部読んでも見つからなくて、まじか〜ってなっていたらHasura側のドキュメントでその対応が書いてありました。💦

同じ様に、両方経験ないけどsupabaseとHasuraに挑戦してみる!みたいな人の役に立ったら嬉しいです。

参考

Hasuraのドキュメントがすごいしっかり書かれているので、順番に読んでいけばできそうです。