はじめに
Supabase Auth を Node.js と組み合わせて使っていたところ、シンプルだと思った設計が実は大きな落とし穴にはまっていたことに気づいたので今回記事にしました。
私自身、
- フロントエンドはできるだけ薄くしたい
- 認証まわりのロジックは Node.js 側に寄せたい
──そう考えて設計を進める中で、
**「refresh が二度走る可能性があるのでは?」**という疑問に行き当たりました。
本記事では、
- なぜ「フロントで refresh しない/バックエンドで refresh する」構成が危険なのか
- Supabase Authをサーバーサイドで使うときのベストプラクティス
を紹介していきます。
問題のある構成
以下は、今回最初に検討していた構成です。

この構成では、認証に関する責務をできるだけバックエンドに寄せることを意図していました。
フロントエンド
- Supabase Auth SDK は使用しない
- バックエンドから受け取ったトークンを保存するだけ
- API リクエスト時に access token, refresh token をそのまま送る
バックエンド(Node.js)
- access token を検証
- 必要に応じて refresh token を使ってリフレッシュ
- 新しいトークンをフロントエンドに返す
フロントエンドは「トークンを理解しない」前提で、
認証ロジックはすべてバックエンドに集約する設計でした。
一見すると責務分離が明確ですが、
Supabase Auth と組み合わせると問題が生じます。
問題点
この構成で最も問題になるのが、access token が期限切れの状態でリクエストが並行して発生するケースです。
Supabase Auth は refresh token rotation(リフレッシュトークンの回転) を採用しています。
これはセキュリティを高めるための仕組みで、
- refresh token は 1 回しか使えない
- refresh が成功すると 新しい refresh token が発行される
- 同じ refresh token が再度使われた場合、不正利用とみなされる
- 不正利用が行われると、そのアカウントに関するすべてのrefresh tokenは失効する。
という性質を持っています。
並行リクエストの流れ
access token がすでに期限切れの状態で、フロントエンドから API リクエストが並行して送られると、次のようなことが起きます。
- フロントエンドはトークンの状態を判断せず、同じ access token, refresh token を使って複数リクエストを送信する
- バックエンドは各リクエストごとに access token を検証し、期限切れと判定する
- 各リクエストが 同じ refresh token を使って refresh を試みる
- 最初のリクエストで得た新しいrefresh tokenも失効する
という問題が発生します。
解決策:Supabase SDK にフロントエンドでrefresh を任せる
結論として、refresh token の扱いはフロントエンドの Supabase SDK に任せるのが最も安全でシンプルです。
今回の問題は「refresh をどこで行うか」に起因していました。
Supabase Auth は refresh token rotation を前提に設計されているため、
この前提に逆らわない構成を取る必要があります。
フロントエンド:Supabase SDK を正しく使う
フロントエンドでは Supabase Auth SDK を使用し、
トークンの保存・更新・取得を SDK に完全に委ねます。
トークンの保存と取り出し
Expo / React Native では、公式ドキュメントにあるセットアップを行えば、
トークンは自動的に安全なストレージへ保存されます。
また
で取り出すことが出来ます。
この方法を使えば、自動的に
- 期限が近付いたらrefreshを行ってくれる
- 二回同じrefresh tokenが送られないように制御してくれる
ため問題が解決します。
バックエンドの役割(refresh しない)
バックエンドでは access token の検証のみ を行い、
refresh token は一切扱いません。
やること
- フロントエンドから送られてきた access token を受け取る
- JWT の署名・期限・発行元などを検証する
- 検証が通ったリクエストのみを処理する
検証方法は公式ドキュメントに記載されている通りです。
やってはいけないこと:setSession の使用
バックエンドで Supabase Client を使う際、
次のように setSession() を使うのは避けるべきです。
この方法では SDK がセッション管理を引き継ぎ、
自動的に refresh が実行される可能性があります。
インスタンス生成時にflagを渡してrefreshされないようにすることも可能ですが、そもそもこの構成ならbackendにrefresh tokenを送る必要がないので、上の公式ドキュメントに記載されている方法で行うのがよいでしょう。
まとめ
Supabase Auth は refresh token rotation を前提に設計された認証基盤です。
この前提を理解せずに refresh をバックエンドで扱うと、
並行リクエスト時に refresh が競合し、セッションが破壊される可能性があります。
今回整理したポイントは以下の通りです。
- access token は即座に失効しないが、期限切れになる
- refresh token は 1 回しか使えず、再利用されると全 refresh token が無効化される
- refresh をバックエンドで行う構成は、排他制御なしでは非常に危険
- Supabase Auth SDK は refresh を安全に制御する前提で作られている
そのため、推奨される構成は次の通りです。
- フロントエンドで Supabase Auth SDK を使用する
- セッションの保存・更新・refresh は SDK に任せる
- API リクエストでは
getSession()で取得した access token を送る - バックエンドは access token の検証のみに専念する
- refresh token はバックエンドに渡さない
この設計にすることで、
- refresh の競合が起きない
- セキュリティを損なわない
- 実装と運用の両方がシンプルになる
という状態を実現できます。