🍆

Supabase認証ヘルパー(auth-helpers)など、Supabaseについて試行錯誤した結果、現時点で知っていることまとめ

2022/08/03に公開

つい先日、クラスメソッドさんの記事で Supabase の auth-helpers に関する情報が上がった影響か、こちらの記事 にて「いいね!」を付けて頂ける方が若干増えたように思います。(数名程度ですが...笑)

これは3ヶ月ほど前に書いた記事でして、自分用メモとして書いたもので駄文が目立ちます。

また、わずか3ヶ月でも auth-helpers 自体も少し変わってきている印象がありますので、その辺りのリンクもまとめ直したいと思います。

Supabaseの日本語情報が少なすぎて試行錯誤しながらヒーヒー言いながら独自に情報収集しています。誤った情報も含まれているかもしれません。ご自身でもよく検証の上、ご活用ください。

まずはじめに

まずはじめに、Supabaseを初めて知った方は Supabase の中の人であるタイラーさんが出演されている下記動画を観ることをオススメします。Supabaseの正体を知ることができるため、理解が加速するものと思います。

https://www.youtube.com/watch?v=XWYkpQRLsFk

Supabase公式はローカルでの開発を推奨している

https://supabase.com/docs/guides/cli/local-development

Supabaseでは素晴らしいGUIが構築されていますが、Supabase公式的にはローカルでの開発を推奨しているようです。詳しい内容は上記リンクをご覧ください。

初めの段階ではSupabaseの雰囲気を掴むためにGUIで色々触ってみて実際に開発を開始するときはローカルに移行するよう意識しておきましょう。

auth-helpers

@supabase/auth-helpers

これまでは「supabase-auth-helpers」という名称でしたが、「auth-helpers」という名称に変わりました。Next.js向けの認証ヘルパーは @supabase/auth-helpers-nextjs というパッケージです。

https://www.npmjs.com/package/@supabase/auth-helpers-nextjs

リファレンス

名称が変わった影響でリファレンスのリンクが消えていました。
推測でURL打ってみたらヒットしました。多分、このページで問題ないはずです。
https://supabase-community.github.io/auth-helpers/index.html

また消えてました><

Next.js向けのドキュメント

Next.js向けのドキュメントは下記にあります。

https://github.com/supabase-community/auth-helpers/blob/main/packages/nextjs/README.md

auth-helpersを使うためのセットアップやクライアントサイドでデータを取得する例などが記載されています。なお、データの取得をする際は対象テーブルの RLS を有効にしておく必要があるので注意しておきましょう。

Next.js 向けのサンプル

Supabaseが公式に提供しているサンプルがあります。

https://github.com/supabase-community/auth-helpers/tree/main/examples/nextjs

認証情報のAPIが提供される

auth-helpers を使うためには、 /pages/api/auth/[...supabase].ts ファイルを作成する必要があります。[...supabase.ts] ファイルには、ドキュメント通りに記述を加えておきましょう。記述を終えると、auth-helpers によって認証周りのAPI機能が提供されます。

http://example.jp が自分のサイトURLだとした場合、

http://example.jp/api/auth/user にアクセスすると、ログイン中のユーザーの情報を参照することができます。
http://example.jp/api/auth/logout にアクセスすると、ログアウトをすることができます。

サーバーサイドで認証判定をすることが可能

auth-helpers には、サーバーサイドでログイン状態を判定する機能が提供されています。

/pages/対象のディレクトリ/_middleware.ts
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/dist/middleware';

export const middleware = withMiddlewareAuth({ redirectTo: '/login' });

この場合、ログインしていなければ /login ページにリダイレクトされます。

※ドキュメントでは import 元が修正されていません。v0.2.5現在では、dist にあるので注意しましょう。

なお、現在(2022年8月3日)は Next.js の Version 12.2系 ではサーバーサイドでログイン判定ができません。Next.jsの Version を 12.1.6 などに戻しておくことで使えるようになります。(Next.js の12.2系からミドルウェアのファイルの置き場所が変わった影響?)本件についてはこちら に記事を書きました。

ログイン判定は独自のパーミッションを作ることもできます。
ユーザーを絞って強固に判定させることもできるため、管理画面等に活用できそうです。

export const middleware = withMiddlewareAuth({
  redirectTo: "/login",
  authGuard: {
    isPermitted: async (user) =>
      (user.email === "hogehoge@example.jp" &&
        user.app_metadata.provider === "twitter" &&
        user.user_metadata.full_name === "hoge") ??
      false,
  },
});

この例では、 hogehoge@example.jp というメールアドレスに一致しつつ、Twitter認証からログインしつつ、Twitterの名前が hoge であるユーザーのみアクセスが有効になります。

Twitter認証を絡ませることで Twitter側とSupabase側のルールに則ったユーザーだけがアクセスできるため、脳みそを使わずに強固なアクセス制限を作ることができそうです。

https://zenn.dev/masa5714/scraps/d0b92b44087e11

CSRで認証後に認証後に要素の表示を振り分けしつつ、transitionしながら表示する(認証判定中はスケルトンスクリーンを表示)

auth-helpers で認証を判定する場合は、 useUser() を使います。この useUser() は非同期的に判定を行ってくれます。そのため、判定が完了したかどうかは useEffect に含めて使用していくことになるかと思います。

// user はユーザー情報が格納されている。
// isLoading は判定が終了したかどうかを教えてくれるもの。
const { user, isLoading } = useUser()

では、特に難しいことはないかと思いきや、transitionを絡ませる場合は若干癖があります。というのも useUser() には 「タブを切り替えた際にも再度判定を行う(trueを保持してくれず、false → true という動きをします。)」という仕様があります。つまり、タブを切り替えるたびに isLoading の値が切り替わってしまい、都度 transition が動く目障りな動きをしてしまいます。

この問題 Recoil に認証結果を保持させて解決をしました。
その代償として auth-helpers の内部的な判定とビューを切り離すことになってしまっています。(個人開発レベルでは影響は無いはず。)

・・・という記事を書きましたので、下記よりご覧ください。

https://zenn.dev/masa5714/articles/00475ff1db2429

認証不要な部分をSSRやISRしたいときは auth-helpers を絡ませない

Supabaseの公式ドキュメントにかかれている通りimport { supabase } from '../utils/supabaseClient' で呼び出した supabase を使って getStaticProps で呼び出すだけで実現できます。

認証が必要な場面でのSSRやISRでは、supabaseServerClientwithPageAuth(もしくはミドルウェア) を組み合わせることで実現ができます。(そもそも、認証が必要なページをSSRやISRをするメリットが無さそうなので出番は限られてくるかも...?ミスったら知らずにGoogle検索でアクセス可能なヤバい情報流出の恐れがあるので手を出したくない領域...。)


ここからは下auth-helpersには関係ありません。Supabaseを初めて使う人が詰まるであろう箇所をまとめます。


Supabaseを初めて使う人へ

Twitter認証をする際の注意点

Twitter認証をする際は、Twitter Developerのアカウントを Elevated に昇格させる必要があります。通常は Essentials です。審査を通過させることで Elevated に昇格できます。

ちなみに審査自体はかなり簡単で、「Supabaseによってセキュアに管理されること」を中心に書くことで迅速に審査をパスすることができました。

審査開始: 5月16日(月) 18:28
審査パス: 5月17日(火) 0:22

https://zenn.dev/masa5714/articles/3b6325ffb4151a

Supabaseではメールアドレスをアカウントと紐づけしているようです。
ですので、他のSNSとの連携の際にうまくいかないときは、メールアドレスが取得できる状態にあるかを見ておくと解決の近道になるかもしれませんね。

GUIが全てではない

SupabaseのGUIは大変見やすく使いやすいです。
しかしながら、このGUIに表示されているものが全てではありません。

例えば、PostgreSQLで使いたいはずの Index や Check制約 はGUIから設定することができません。全てSQL Editor上 もしくは pgAdmin などの外部ツールを用いてSQL文を書いて実行する必要があります。

GUIに無いからできないんかな? となるシーンがあるかと思いますが、中身はPostgreSQLですので、PostgreSQLでできることはほとんどできるかと思います。

バリデーションは恐らくCHECK制約でやっていく。

(バリデーションという表現は正しくないかも?送信されてきたデータが不正ではないかをチェックする仕組みのことを指しています。クライアントサイドで完結し、PHPのように中間でデータを検証することが無い想定です。)

Supabaseでバリデーション機能が用意されている様子はありませんでした。
PostgreSQLのCHECK制約を書いていき、InsertやUpdateの際にCHECK制約で定義したルールに則って Insert / Updateの可否を自動判定して処理してくれます。

PostgreSQLおよびSQL文に慣れていないと扱いに難しい部分が出てくる印象です。とはいえ、SupabaseでPostgreSQLデビューをした僕でもある程度動かすことができているので、皆さんなら問題なく実装可能かと思います。

何かが挿入されたら他テーブルに何かをするという処理もPostgreSQLの関数で実現可能です。(むしろPHPなどの中間のプログラムが入らないのでシンプルになる感覚です。PHPって要らない子だったんだ...という気持ちが湧いてきています。)

https://zenn.dev/masa5714/articles/4f0ae0b2f511ca

果たしてこれがバリデーションとして正しい方法なのかは現時点でも分かっていません...。

サインアップしたタイミングで任意のテーブルにプロフィールを追加することが可能

Supabaseにはトリガーという機能があります。(正しくは PostgreSQL の機能です)
トリガーを使うことで、任意のテーブルに何らかのイベントがあった際に、任意の関数を実行させることが可能なります。

例えば、 hoge というテーブルにデータが入った際、 poge というテーブルにもデータをコピーすることができます。

Supabaseのユーザーデータは authスキーマにある usersテーブルに格納されています。

ですので、 auth.users にInsertやUpdateイベントが発生したタイミングで任意のテーブルに対して Insertする処理を書くだけでそのユーザーデータをコピーして任意のカラムを付与して独自のプロフィール機能を構築できるようになります。

そんな内容を下記に駄文でまとめていますので参考にしてみてください。

https://zenn.dev/masa5714/articles/960863f01b20e0

※トリガーを実装する際はSQLインジェクションに気をつけてください。トリガーで取得した値にSQL文を含められると攻撃される恐れがあります。(ChatGTPさんが対策方法教えてくれるので聞いてみてください。)

PostgreSQLでは正規表現を多用するかも?正規表現苦手な人に一筋の光を。

Google検索「site:https://www.regextester.com/ hiragana」等で検索してみてください。結果では こちらのページ にたどりつくはずです。

このページは正規表現を作ってテストできるサイトでして、色々な人がテストした残骸(?)を覗き見ることができます。中国語っぽい正規表現だったり、マニアックな正規表現を知るきっかけになるかと思いますので活用してみてください。

https://zenn.dev/masa5714/articles/3266485db9eab7

CSVのインポートは超カンタン!

SupabaseのGUIから New table画面で「Import data via spredssheet」ボタンからCSVやテキストを入力してデータを挿入することができます。

カラム名もCSVに書いておけば勝手に入れてくれます。
12万件程度のデータであれば30秒程度で処理を完了してくれました。(記憶が定かではない)

GUIのSQL EditorからはRAISE INFOが使えない

PostgreSQLで変数の中身を確認したいとき、 RAISE INFO を使うことでチェックできます。PHPで言うところのvar_dumpやJavaScriptで言うところのconsole.logみたいなやつです。これを使うためには、他のツールを使う必要があります。

個人的にはpgAdminが使いやすかったです。

※raise logというものを使うとGUIでも確認できるらしいです。(未検証)
https://github.com/supabase/supabase/discussions/9385

jsonbのデータを挿入するときの注意点

SupabaseというよりはPostgreSQLの注意点かもしれません。
jsonbを挿入する際、JavaScriptで JSON.stringify() だったり、 SQL文の to_jsonb() でデータを入れようとするかもしれません。しかし、これをしてしまうと、 SELECT文で contact->>'tel' のように引っ張り出すことができなくなります。

SupabaseのテーブルのGUIでデータが入ったマス(名称分からない...。)をダブルクリックして生データを確認し、 \ が入ったエスケープされた状態になっていると改善が必要です。

jsonbのデータを挿入する際は { "tel": "0000-000-000", "email": "hoge@hogehoge.com" } のようにそのままJSON形式で挿入するようにしましょう。jsonbを取り扱うときは正常にselect文でデータを取れるか必ず確認してください。(やらかしてしまい、ちゃんと使えるjsonbデータに直すの辛かったです...。)


ここからは下は現時点で個人的に悩んでいることです。解決策に心当たりがある方はコメントから助けてください!!!


個人的に困っていること。

定期的にスクレイピングしたデータをどうやって本番DBに入れていくのか?

初回のINSERTについては問題無いのですが、現状のSupabaseではUPDATEのときは1件ずつしかデータ登録ができない仕様です。スクレイピング結果を1件ずつ都度リクエストを送るのは何か違うような気がしています。そもそもスクレイピングしたデータを皆さんどうやって本番DBに反映させてるんですかね...?何かフラグを立てておいて、データが揃ったタイミングで公開フラグにするとかそういう感じ?もしくは結構力技で強行突破してるんかな...?

2023年3月17日追記:
Supabaseはエグレス(output方向)のカウントはするが、イングレス(input方向)はカウントしないらしい。つまりどんだけINSERTしようとも追加費用はかからないっぽいのでそんなにきにしなくてもいいかも。また、1秒間に1000件のINSERTを捌くことができるらしい。(Githubで中の人が書いてたんだけど、どこのページか見失ってしまった><)

Discussion