Supabase の鍵まわりで事故が起きるのは、権限差を知らないからではありません。「service_role をフロントに置くな」までは、多くの現場が把握しています。それでも詰まる理由は、鍵が4種類あるうえに世代が2つ並走し、さらにアクセストークンやプロジェクト URL のように性質の違う値が同じ画面付近に並ぶ点にある。加えて、漏れたときに取れる手が鍵の世代で変わります。この記事では2026年8月時点の公式表記を前提に、発行物の棚卸しから漏洩時の段取りまでを実装順に並べます。
まとめ:Supabase APIキー管理で先に決める4点
第一に、いま新規で組むなら sb_publishable_ と sb_secret_ の新形式を採り、レガシーの anon と service_role は移行対象として扱ってください。公式は後者を「end of 2026」までに非推奨とする方針を示しています。
第二に、公開してよい値はプロジェクト URL と publishable key(またはレガシーの anon key)の2つだけと決め切ること。第三に、secret 鍵とアクセストークンは名前空間から分けて保管し、サーバー実行環境と CI の秘密ストアにしか置かないでください。
第四が本記事の芯で、レガシーの anon・service_role・JWT secret はローテーションの手段が既に廃止されており、漏れても回せません。新形式への移行は機能の刷新ではなく、漏洩時に取り返しがつく状態を用意する作業だと捉えてください。
Supabaseが発行する鍵とトークンの全体像と、この記事で扱う範囲
4種のAPIキーとアクセストークン・プロジェクトURLの棚卸し
API キーは現行世代と旧世代で計4種です。公式の Understanding API keys は次のように整理しています。
| 種別 | 形式 | 権限 | 公開の可否 | RLSの回避 |
|---|---|---|---|---|
| publishable key | sb_publishable_ |
低 | 可 | しない |
| secret key | sb_secret_ |
高 | 不可 | する |
| anon key(旧) | JWT | 低 | 可 | しない |
| service_role key(旧) | JWT | 高 | 不可 | する |
ここに、API キーではない発行物が2つ加わります。ひとつがパーソナルアクセストークンで、sbp_ で始まり、CLI と Management API の認証に用いる値です。もうひとつがプロジェクト URL で、こちらは秘密ではありません。
アクセストークンの性質は API キーとまったく違います。API キーがプロジェクト単位でデータ API の入口を守る値である一方、アクセストークンはアカウントの権限そのものを持ち、プロジェクトの作成や削除、設定変更まで届く。置き方を誤ったときの被害範囲は secret 鍵より広いと考えてください。CLI 側での受け渡しはSupabase CLIとローカル開発環境を扱った記事で、反映ジョブの具体例として整理しています。
新形式とLegacyの2タブに分かれた取得場所と探す先の違い
「anon key はどこにあるか」という問いには、世代で答えが分かれます。ダッシュボードの Settings から API Keys を開くと、新形式は「Publishable and secret API keys」のタブに、旧形式は「Legacy API Keys」のタブという配置です。プロジェクトを作った時期によって、初期表示のタブが違う場合があります。
アクセストークンは同じ場所にありません。プロジェクト配下ではなくアカウント配下の dashboard/account/tokens で発行します。発行直後の一度しか全文を表示しない仕様なので、その場で秘密ストアへ入れてください。
セルフホストではこの画面ではなく、環境変数ファイルへ自分で鍵を生成して置きます。生成コマンドと新旧の鍵が併存する事情はSupabaseセルフホストの構成と運用の記事で扱っています。
ポリシー記述とフレームワーク側の結線を既存記事へ委譲する範囲
ポリシーの書き方、auth.uid() の使い方、有効化漏れの検知はSupabase RLSの設定手順を扱った記事を参照してください。行レベルの制御そのものが初見なら、製品に依存しない一般論をRow Level Security(RLS)の仕組みを整理した記事で確かめてから戻ると読み進めやすくなります。関数側でのシークレット登録はSupabase Edge Functionsの記事、フレームワークからの読み込みはNext.js連携の記事とVercelデプロイ連携の記事が担当します。
publishableとsecretの権限差と、送信仕様が変わった箇所
BYPASSRLSを持つsecret鍵がポリシーを飛ばす経路と影響範囲
secret 鍵(および旧 service_role key)は、Postgres の BYPASSRLS 属性を持つロールとしてリクエストを実行します。書いたポリシーは評価されず、テーブルの全行に対して読み書きが通る状態です。publishable 鍵の側は anon ロール、ログイン後は authenticated ロールとして動き、ポリシーの評価を受けます。
押さえておきたいのは、影響範囲が1テーブルの全行ではなく公開スキーマの全テーブルに及ぶ点です。ポリシーを丁寧に書いた画面が10本あっても、secret 鍵が1本漏れれば全部が素通しになります。RLS の設計と鍵の保管を別々の防壁として数えるのではなく、鍵の保管が破れた時点で RLS の防壁は無いものと見積もるのが安全側の前提でしょう。
この見積りは公開前の点検にも影響します。ポリシーのレビューと同じ回数だけ、鍵の置き場所を追ってください。自社で点検体制が組めない段階なら、外部の脆弱性診断・セキュリティ診断で鍵の露出と権限設定を一度洗い出す進め方もあります。
ブラウザからの利用に401を返す保護を防壁と数えてはいけない理由
新形式には旧形式に無かった保護が入りました。公式ドキュメントは secret 鍵について「always reply with HTTP 401 Unauthorized」と記し、ブラウザから使われた場合は拒否すると説明しています。判定は User-Agent ヘッダーの照合です。
この挙動は、誤ってフロントへ貼り付けたときに気づける仕組みとして有用でしょう。ただし防壁として数えないでください。User-Agent は送る側が書き換えられる値で、鍵が漏れた後にブラウザ以外の経路から呼ばれれば通ります。移行中にフロントが 401 で止まったら、publishable 鍵を置くべき箇所に secret 鍵が入っていないか最初に疑ってください。
apikeyヘッダー限定という仕様がpg_netとWebhookを壊す箇所
移行で見落としやすいのが送信方法の変更です。公式ガイドは「You can’t send a publishable or secret key in the Authorization: Bearer header. Send it on the apikey header instead.」と明記しています。新形式は JWT ではないため、Bearer トークンとして解釈される経路では受け付けられません。この鍵で叩くエンドポイントの構造と関数呼び出しの設計はSupabase 自動生成API(PostgREST)とRPCの使い分けで整理しています。
影響を受けるのは、データベース側から外へ叩く経路です。Database Webhooks と pg_net の呼び出しでは、旧来 Authorization: Bearer のヘッダーへ service_role key を載せていた例が多く、ここを apikey ヘッダーへ書き換える作業が要ります。公式は新しい secret 鍵を「aren’t JWTs, so they’re rejected there」と説明しています。
フロントへ出してよい値の線引きと、環境変数の命名で線を引く設計
NEXT_PUBLIC_を付けてバンドルへ入れてよい値の判断基準
判断基準は1つに絞れます。ポリシーの評価を受ける値だけがバンドルへ入ってよい、です。この基準に照らすと、プロジェクト URL と publishable key(またはレガシーの anon key)が該当し、secret 鍵とアクセストークンは該当しません。
Next.js なら、公開してよい値には NEXT_PUBLIC_SUPABASE_URL のようにプレフィックスを付け、公開してはいけない値には付けない。プレフィックスの有無が露出範囲そのものを表すため、命名で線が引かれる形です。逆側の事故として多いのは、サーバー専用の値へ公開プレフィックスを付けてしまう取り違えでしょう。公開プレフィックス付きの変数名に SECRET や SERVICE_ROLE が混じっていないかを CI で機械的に落とすと、レビューの負荷が下がります。
プロジェクトURLとアクセストークンを同じ箱に入れない配置設計
プロジェクト URL を秘密扱いすると、フロントへ渡すための例外運用が生まれ、その例外に secret 鍵が紛れ込みます。秘密ではないという扱いを徹底しましょう。一方でアクセストークンは、プロジェクト単位の鍵とは別の箱へ入れてください。アカウント権限で動くため、複数プロジェクトを預かる受託の現場では被害範囲が案件をまたぎます。
整理すると、置き場所は3層に分かれます。クライアントのバンドルへ入る層(URL と publishable)、サーバー実行環境だけが読む層(secret)、運用オペレーションのための層(アクセストークンとデータベースのパスワード)です。層をまたいで同じ変数名を使い回さないことが、後の棚卸しを楽にします。
CI/CDと外部の連携先へ渡す鍵を用途ごとに分けて持たせる手順
新形式で使える設計として、secret 鍵は複数本を作れます。公式ガイドも同じ SUPABASE_SECRET_KEYS のオブジェクト内に名前を変えた複数キーを保持できると説明しています。旧世代では service_role key が1本しか無く、外部サービスへ渡す鍵とアプリが使う鍵を分けられませんでした。
分ける単位は「止めたいときに単独で止められる粒度」で決めてください。実務では、アプリのサーバー用、バッチ用、外部 SaaS 連携用といった3本から4本に分ける形が扱いやすい範囲でしょう。1本に集約すると、連携先1つの都合で全系統の鍵を差し替える必要が生じます。
渡し方の原則は、他社の管理画面へ値を貼るのは連携用の鍵に限り、アプリ用の鍵は自社の秘密ストアから出さないという分離です。プラットフォーム側が環境変数を自動で同期する構成では、同期される値の範囲を先に確かめてから追加分を手で入れてください。同期の仕様と再デプロイの要否はVercelデプロイ連携の記事で整理しています。
レガシー鍵から新形式のキーへ移行する6工程と追加作業が要る箇所
新しいキーを作成してからレガシー鍵を無効化するまでの実行順序
公式ガイドの手順は6段です。まず Settings の API Keys で新形式のタブを開き、「Create new API keys」を押して default という名前の組を作ります。次にクライアント側のコードで anon key を publishable key へ置き換える。RLS ポリシーの挙動は変わりません。
3段目でバックエンドの service_role key を secret 鍵へ替えます。4段目が Edge Functions の更新で、環境変数から新しい鍵を読むように直し、必要なら verify_jwt の設定を見直す。5段目が棚卸しで、クライアント、CI/CD パイプライン、サードパーティ連携、cron ジョブ、Webhook のすべてでレガシー鍵の利用が残っていないかを確認します。最後に Settings の API Keys でレガシー鍵を無効化してください。
公式は「you can swap clients one at a time and deactivate the legacy keys only after nothing depends on them」と述べています。無効化を先に済ませてから差し替えようとすると全系統が同時に落ちるため、この順序は入れ替えないでください。
Edge Functionsとpg_netで手当てが要る理由
4段目と5段目に追加作業が集まる理由は、鍵の形式が JWT から独自形式へ変わったことに尽きます。JWT を前提に検証や送信を組んでいた箇所が、そのままでは通らなくなるからです。Edge Functions は新形式の apikey ヘッダーを自動検証しないため、verify_jwt = false を設定するか、対応済みの SDK へ寄せる判断が要ります。
加えて Realtime の扱いにも記載があり、公開の Realtime 接続は利用者単位の認証で昇格しない限り24時間に制限されると説明されています。手当ての漏れは数日後に「夜間バッチだけ落ちる」形で出ます。cron と Webhook は日常の画面操作で踏まないため、棚卸しのリストへ明示的に載せてください。
レガシー鍵の利用棚卸しをどこまで終えたら無効化に踏み切れるか
踏み切る条件は、ログで裏を取ることです。コードを検索して該当が消えたことは条件になりません。外部 SaaS の設定画面や、退職者が作った連携が残っている可能性を、検索では拾えないからです。
実務的には、棚卸し後に一定期間レガシー鍵経由のリクエストが0件だと確認してから無効化する流れが安全でしょう。期間の目安は、月次バッチを抱えているなら1か月、日次までなら1週間から2週間。この待ち時間を工程表へ最初から入れておくと、無効化が先送りされにくくなります。
漏洩時に取れる手は鍵の世代で変わる:レガシー鍵は回せないという事実
レガシー鍵のローテーション機能が既に廃止されているという事実
公式のトラブルシューティング記事は「it is no longer possible to rotate the legacy anon, service and JWT secrets」と述べています。かつて存在したレガシー鍵の再生成機能は、既に提供されていません。
この一文の実務的な意味は重いものです。レガシーの service_role key を漏らした場合、その鍵を無効化する手段は「新形式へ移行してレガシー鍵そのものを無効化する」道しかない。つまり事故対応の最中に、平時の移行作業を一気に走らせることになります。落ち着いて6工程を踏む余裕は、その状況では残っていないでしょう。
移行の位置づけは、機能の追随ではなく事故対応の準備です。「まだレガシーで動いているが困っていない」という状態は、正確には「漏れた瞬間に打てる手が無い状態」を指します。
secret鍵の削除が不可逆であるという前提での複数鍵の持ち方
新形式では回せます。ローテーションの操作は「新しい secret 鍵を作り、古い鍵を削除する」という2手で、公式は削除が不可逆であると記しています。復活はできないため、削除の前に参照元の差し替えを終えてください。
ここで前述の複数鍵が効いてきます。用途ごとに分けておけば、漏れた1本だけを新しく作って差し替え、旧1本を消すだけで収まる。1本に集約していると差し替えの対象が全系統になり、停止時間の見積りが読めません。新鍵へ参照元を移し、並走させたうえで旧鍵を削除する形が扱いやすいでしょう。
JWT署名キーのローテーションが全接続を切る挙動と実施の段取り
API キーとは別に、Auth が発行するアクセストークンの署名鍵の移行があります。こちらへ移ると、Project Settings の JWT Keys で「Rotate Keys」を実行できるようになり、現行鍵が「Previously used keys」へ移った後、明示的に「Revoke」する流れです。ES256 への移行手順と getClaims() でのローカル検証はSupabase Authの署名鍵移行とローカル検証を扱った記事で解説しています。
挙動として押さえるべきは、旧来の JWT secret を再生成した場合の影響でしょう。公式は「Once the JWT secret is regenerated, all current API secrets will be immediately invalidated, and all connections using them will be severed.」と記しています。既存のセッションと接続が即時に切れる操作なので、実施時刻の選定と利用者への周知が要ります。
段取りとしては、API キーの移行と JWT 署名鍵の移行を同じ日に重ねないでください。両方を同時に動かすと、障害が出たときにどちらの変更が原因か切り分けられません。1週間ほど間隔を空け、間にログの確認を挟む形が安全です。
漏洩の検知を仕組みへ寄せる手順と、新形式でスキャンが効く理由
プレフィックス付きの新形式がシークレットスキャンで拾える仕組み
GitHub の Supported secret scanning patterns には Supabase のトークンが4種類登録されており、そのうち Secret Key と Personal Access Token はプッシュ保護の対象です。sb_secret_ や sbp_ という固定のプレフィックスを持つため、機械的な照合ができるからです。
レガシーの service_role key は JWT 形式で、先頭が汎用の JWT と同じ eyJ になります。他の用途の JWT と区別できないため、この種のスキャンでは拾いにくい値でしょう。新形式への移行は、漏洩時に回せるようになるだけでなく、漏洩を検知できるようになる変更でもあるという理解が実態に近いはずです。
ビルド成果物とリポジトリを機械的に見る確認手順のCI組み込み
スキャンで拾うのはリポジトリ側なので、ビルド成果物の側も見てください。本番と同じ設定でビルドした出力に対して sb_secret_ と sbp_ の文字列、そしてレガシー鍵を使っているなら service_role という文字列を検索し、0件であることを確かめます。
この検査は CI の1ステップとして組み込めます。ビルド後にデプロイ前へ挟み、検出したらジョブを落とす。命名を変えた直後の取り違えも捕まえられます。
鍵の管理だけでは守り切れない3つの条件と、設計変更の判断基準
鍵の管理を締めても足りない場面を条件で示します。該当するなら、構成そのものを変える判断を勧めます。
1つ目は、RLS を有効にしていないテーブルが公開スキーマに残っている場合です。この状態では publishable 鍵が実質的に全権になり、「公開してよい鍵」という前提が崩れます。先にポリシーを入れてください。2つ目は、利用者の操作を代行する処理で secret 鍵を常用している場合です。ポリシーを飛ばすため、認可の検証手段が失われます。管理バッチや外部 Webhook の受信のように利用者文脈を持たない処理に限って使い、画面からの操作は publishable 鍵とポリシーで通す構成へ寄せてください。
3つ目は、複数の顧客プロジェクトを1つのアクセストークンで回している場合です。案件をまたいで被害が広がる構造なので、トークンを案件ごとに分けるか、CI から到達できる範囲を絞る設計へ変える価値があります。逆に3つとも該当せず、鍵が新形式で用途別に分かれ、検知が CI に載っているなら十分な水準でしょう。製品の選定段階から見直す場合はSupabaseとFirebaseの違いを比較した記事で全体像を確かめてください。
よくある質問
anon keyはどこで確認できますか?
ダッシュボードの Settings から API Keys を開き、「Legacy API Keys」のタブを選ぶと表示されます。新形式の publishable key と secret key は同じ画面の別タブです。プロジェクトの作成時期によって初期表示のタブが違うため、両方のタブを確かめてください。
anon keyをフロントに置いても本当に安全ですか?
RLS が有効で、ポリシーが意図どおりに書かれている場合に限り安全です。この鍵は anon ロールとしてポリシーの評価を受けるため、公開前提で設計された値です。ただし RLS 未有効のテーブルが残っていると、そのテーブルは全件読み書きできる状態になります。
アクセストークンとAPIキーはどう違いますか?
権限の及ぶ範囲が違います。API キーはプロジェクトのデータ API に対する鍵で、アクセストークン(sbp_ で始まる値)はアカウントの権限で CLI や Management API を操作するための資格情報です。後者はプロジェクトの設定変更まで届くため、保管先を分けて扱ってください。MCP接続でこのアクセストークンを使う場面と、ホスト型では発行そのものが不要になる条件はSupabase MCPサーバーの接続手順とスコープ設計で整理しています。
service_role keyが漏れたときはどうすればよいですか?
レガシー形式の場合、その鍵を単独で回す手段はありません。新形式の鍵を作って全参照元を差し替え、レガシー鍵を無効化してください。新形式の secret 鍵であれば、新しい鍵を作って差し替えたうえで漏れた鍵を削除できる。削除は取り消せないので、差し替えを終えてから実行してください。
新形式のキーへ今すぐ移行する必要はありますか?
期限で急ぐ必要はありませんが、先に済ませる価値はあります。公式はレガシー鍵を「end of 2026」までに非推奨とする方針を示しており、両形式は並走できる仕様です。前倒しする理由は期限ではなく、漏洩時に回せる状態と検知できる状態が同時に手に入る点にあります。
関連記事
- Supabase RLSの設定手順とAuth連携ポリシー設計|有効化漏れの検知まで:ポリシー本体を扱います。
- SupabaseとFirebaseの違いを徹底比較|料金・認証・どっちを選ぶか:製品選定の全体像です。
- Supabase Next.js連携の実装|proxyとgetClaimsで組むApp Router構成:命名と実装を解説。
- Supabase Vercelデプロイ連携|環境変数の自動同期とプレビュー環境のDB分離:同期の範囲が分かります。
- Supabase Edge Functionsとは:Deno実行環境とデプロイ・シークレット・課金単位:関数側の登録手順。
- Supabase CLIとローカル開発環境:db diffで進めるマイグレーションとCI:CIへ渡す例があります。