AuthApiError: Email not confirmed in Supabase: why new users can't sign in
A new person signs up, tries to sign in, and your app says "Email not confirmed". In code it comes back as an AuthApiError with the code email_not_confirmed; public reports show the raw response as {"code":"email_not_confirmed","message":"Email not confirmed"} (Supabase issue #29632). The account exists, but the person is locked out of it.
Supabase's error reference describes it as: "Signing in is not allowed for this user as the email address is not confirmed." The project is doing what it was told. With Confirm email on, users "will need to confirm their email address before signing in for the first time", and on hosted Supabase projects it is on by default. On self-hosted projects and in local development it is off by default, which is why an app can behave differently on a laptop than live.
So the real question is why this person is still unconfirmed. Supabase also advises identifying errors by error.code, not by matching the message text. Here are six reasons, in the order to check them.
1. The confirmation email was never sent or never arrived
Supabase's default email service only delivers to addresses on your project's team, at 2 messages per hour, and "is not meant for production use".
Fix:
- Open your project's Auth logs and look for an error at the time of the sign-up. Supabase names this as the first step for undelivered auth emails.
- If you are on the default email service, set up your own SMTP provider and enable custom SMTP in the Authentication settings. Supabase lists Resend, AWS SES, Postmark, Twilio SendGrid, ZeptoMail and Brevo as examples.
- Set up SPF, DKIM and DMARC for the sending domain. Supabase recommends this for deliverability.
Sources: Supabase: Send emails with custom SMTP · Supabase troubleshooting: Not receiving Auth emails
2. The email arrived, but the link was already used up
Supabase documents that some email providers' security features prefetch links in incoming emails. The one-time link is consumed before the person clicks it; they see "Token has expired or is invalid" and stay unconfirmed.
Fix:
- Open the Email Templates page in the dashboard and edit the Confirm signup template.
- Choose one of Supabase's two documented options: send a one-time code by putting {{ .Token }} in the template and verify it with supabase.auth.verifyOtp, or link to a page in your app with a button that opens the real confirmation link.
- Send the affected person a new confirmation email (see cause 4).
Source: Supabase: Email templates
3. The link sent them to localhost or a page that does not finish the job
Supabase's Site URL defaults to http://localhost:3000, and it is used whenever the app does not name an approved redirect. With the PKCE flow, Supabase's template sends the person to {{ .SiteURL }}/auth/confirm?token_hash=..., so confirmation only completes if that page exists on your live app.
Fix:
- Open URL Configuration in the dashboard and change the Site URL from localhost to your live address.
- Add your live address to the Redirect URLs list, for example https://yourapp.com/**.
- In the sign-up call, pass options: { emailRedirectTo: 'https://yourapp.com/welcome' }. The address must be on the Redirect URLs list.
- If your template uses a token-hash link, make sure your live app has the page it points to.
Sources: Supabase: Redirect URLs · Supabase: Password-based auth
4. They tried to sign in again, expecting a new email
Signing in does not send a new confirmation email. Someone who keeps trying only gets the same refusal. Lovable's documentation says it plainly: "signing in again does not resend it".
Fix:
- Catch email_not_confirmed in your sign-in screen and show a "Resend confirmation email" button instead of a generic error.
- Have the button call supabase.auth.resend({ type: 'signup', email, options: { emailRedirectTo } }).
- Disable the button for at least 60 seconds after each send. Supabase's default allows one signup confirmation request per user per 60 seconds.
- On an app on Lovable's built-in backend without such a button, Lovable's documented route is for the person to sign up again with the same address.
Sources: Supabase JavaScript reference: resend() · Supabase: Rate limits · Lovable: Email authentication
5. The account was created outside the normal sign-up
Users created with the admin API (auth.admin.createUser) without email_confirm: true can be left unconfirmed, and then hit the same error. Public reports describe this even after Confirm email was switched off (#29632).
Fix:
- Confirm the account on the server with supabase.auth.admin.updateUserById(id, { email_confirm: true }). Supabase says this function "should only be called on a server", and never with the service_role key in the browser.
- On Lovable's built-in backend, accounts you create under More, Users, Add user, Create new user are confirmed automatically.
Sources: Supabase: auth.admin.updateUserById() · Lovable: Email authentication
6. The app assumes people are signed in right after sign-up
Confirmation is one switch for the whole project. If your screens send a new person straight into the app after sign-up, the next sign-in attempt meets this error.
Fix:
- Decide whether you want confirmation. Supabase keeps it on by default for hosted projects. In Lovable the matching setting is Auto-confirm email, and Lovable recommends keeping confirmation on for live apps.
- If confirmation stays on, show a "check your email" screen after sign-up instead of opening the app.
- Test the full path with a brand-new address, not your own account.
Sources: Supabase: General configuration · Supabase: Password-based auth
Why you can't see it yourself
You're signed in, your customers aren't. You confirmed your own account long ago, from an address that is on the team. You never sit where a new person sits: signed up, unconfirmed, waiting for an email that may never come.
FAQ
I switched off "Confirm email", but users still get "Email not confirmed". Why? People report this on Supabase's GitHub, for example #29632 and #5113. For an account that is already stuck, confirm that account: send a new confirmation email with resend(), or confirm it on the server with updateUserById.
Can people use the app before confirming their email? Only by switching confirmation off for the whole project. A request for a middle ground (supabase/auth #1494) was closed in 2024, with anonymous sign-in suggested as the alternative.
Fixed it? Prove a real stranger gets in
The test that matters is a brand-new address signing up, getting the email, clicking the link and signing in on your live app. Your own account cannot show that, because it is not new. Your AI reads your code. VibeFix signs up like a stranger.
Free check. Paste your live app's link at vibe-fixer.com: an AI agent signs up to your live app once as a brand-new person, with a real inbox of its own, on a phone and on a computer. You get the problem count, what each problem is, and one screenshot. It never changes your app and never emails your users.
$9 for one app. Shows which cause is breaking your app and where, a fix prompt written for your app for every problem, plus 3 re-checks so you can see whether the fix holds for a stranger. It does not promise a fix; it shows what the agent saw.
Related: Supabase "Error sending confirmation email" (500) · Supabase confirmation email not sending · Supabase redirect URL localhost
VibeFix is independent. Supabase and Lovable are trademarks of their owners.
Does your sign-in screen tell an unconfirmed user what to do next, or does it only show the error?
Originally published at vibe-fixer.com. I'm building VibeFix, an AI agent that signs up to your live app like a brand-new person and shows what breaks.
Mentions
AuthApiError: Email not confirmed in Supabase: why new users can't sign in A new person signs up, tries to sign in, and your app says "Email not confirmed". In code it comes back as an AuthApiError with the code email_not_confirmed; public reports show the raw response as {"code":"email_not_confirmed","message":"Email not confirmed"} (Supabase issue #29632). The account exists, but the person is locked out of it. Supabase's error reference describes it as: "Signing in is not allowed for this user as the email address is not confirmed." The project is doing what it was told. With Confirm email on, users "will need to confirm their email address before signing in for the first time", and on hosted Supabase projects it is on by default. On self-hosted projects and in local development it is off by default, which is why an app can behave differently on a laptop than live. So the real question is why this person is still unconfirmed. Supabase also advises identifying errors by error.code, not by matching the message text. Here are six reasons, in the order to check them. 1. The confirmation email was never sent or never arrived Supabase's default email service only delivers to addresses on your project's team, at 2 messages per hour, and "is not meant for production use". Fix: - Open your project's Auth logs and look for an error at the time of the sign-up. Supabase names this as the first step for undelivered auth emails. - If you are on the default email service, set up your own SMTP provider and enable custom SMTP in the Authentication settings. Supabase lists Resend, AWS SES, Postmark, Twilio SendGrid, ZeptoMail and Brevo as examples. - Set up SPF, DKIM and DMARC for the sending domain. Supabase recommends this for deliverability. Sources: Supabase: Send emails with custom SMTP · Supabase troubleshooting: Not receiving Auth emails 2. The email arrived, but the link was already used up Supabase documents that some email providers' security features prefetch links in incoming emails. The one-time link is consumed before the person clicks it; they see "Token has expired or is invalid" and stay unconfirmed. Fix: - Open the Email Templates page in the dashboard and edit the Confirm signup template. - Choose one of Supabase's two documented options: send a one-time code by putting {{ .Token }} in the template and verify it with supabase.auth.verifyOtp, or link to a page in your app with a button that opens the real confirmation link. - Send the affected person a new confirmation email (see cause 4). Source: Supabase: Email templates 3. The link sent them to localhost or a page that does not finish the job Supabase's Site URL defaults to http://localhost:3000, and it is used whenever the app does not name an approved redirect. With the PKCE flow, Supabase's template sends the person to {{ .SiteURL }}/auth/confirm?token_hash=..., so confirmation only completes if that page exists on your live app. Fix: - Open URL Configuration in the dashboard and change the Site URL from localhost to your live address. - Add your live address to the Redirect URLs list, for example https://yourapp.com/**. - In the sign-up call, pass options: { emailRedirectTo: 'https://yourapp.com/welcome' }. The address must be on the Redirect URLs list. - If your template uses a token-hash link, make sure your live app has the page it points to. Sources: Supabase: Redirect URLs · Supabase: Password-based auth 4. They tried to sign in again, expecting a new email Signing in does not send a new confirmation email. Someone who keeps trying only gets the same refusal. Lovable's documentation says it plainly: "signing in again does not resend it". Fix: - Catch email_not_confirmed in your sign-in screen and show a "Resend confirmation email" button instead of a generic error. - Have the button call supabase.auth.resend({ type: 'signup', email, options: { emailRedirectTo } }). - Disable the button for at least 60 seconds after each send. Supabase's default allows one signup confirmation request per user per 60 seconds. - On an app on Lovable's built-in backend without such a button, Lovable's documented route is for the person to sign up again with the same address. Sources: Supabase JavaScript reference: resend() · Supabase: Rate limits · Lovable: Email authentication 5. The account was created outside the normal sign-up Users created with the admin API (auth.admin.createUser) without email_confirm: true can be left unconfirmed, and then hit the same error. Public reports describe this even after Confirm email was switched off (#29632). Fix: - Confirm the account on the server with supabase.auth.admin.updateUserById(id, { email_confirm: true }). Supabase says this function "should only be called on a server", and never with the service_role key in the browser. - On Lovable's built-in backend, accounts you create under More, Users, Add user, Create new user are confirmed automatically. Sources: Supabase: auth.admin.updateUserById() · Lovable: Email authentication 6. The app assumes people are signed in right after sign-up Confirmation is one switch for the whole project. If your screens send a new person straight into the app after sign-up, the next sign-in attempt meets this error. Fix: - Decide whether you want confirmation. Supabase keeps it on by default for hosted projects. In Lovable the matching setting is Auto-confirm email, and Lovable recommends keeping confirmation on for live apps. - If confirmation stays on, show a "check your email" screen after sign-up instead of opening the app. - Test the full path with a brand-new address, not your own account. Sources: Supabase: General configuration · Supabase: Password-based auth Why you can't see it yourself You're signed in, your customers aren't. You confirmed your own account long ago, from an address that is on the team. You never sit where a new person sits: signed up, unconfirmed, waiting for an email that may never come. FAQ I switched off "Confirm email", but users still get "Email not confirmed". Why? People report this on Supabase's GitHub, for example #29632 and #5113. For an account that is already stuck, confirm that account: send a new confirmation email with resend(), or confirm it on the server with updateUserById. Can people use the app before confirming their email? Only by switching confirmation off for the whole project. A request for a middle ground (supabase/auth #1494) was closed in 2024, with anonymous sign-in suggested as the alternative. Fixed it? Prove a real stranger gets in The test that matters is a brand-new address signing up, getting the email, clicking the link and signing in on your live app. Your own account cannot show that, because it is not new. Your AI reads your code. VibeFix signs up like a stranger. Free check. Paste your live app's link at vibe-fixer.com: an AI agent signs up to your live app once as a brand-new person, with a real inbox of its own, on a phone and on a computer. You get the problem count, what each problem is, and one screenshot. It never changes your app and never emails your users. $9 for one app. Shows which cause is breaking your app and where, a fix prompt written for your app for every problem, plus 3 re-checks so you can see whether the fix holds for a stranger. It does not promise a fix; it shows what the agent saw. Related: Supabase "Error sending confirmation email" (500) · Supabase confirmation email not sending · Supabase redirect URL localhost VibeFix is independent. Supabase and Lovable are trademarks of their owners. Does your sign-in screen tell an unconfirmed user what to do next, or does it only show the error? Originally published at vibe-fixer.com. I'm building VibeFix, an AI agent that signs up to your live app like a brand-new person and shows what breaks.
MCP クライアントが OAuth でMCP サーバにつながる仕組み Claude アプリの設定で MCP サーバーの URL を貼ると、ブラウザでログイン画面が開き、「許可する」を押すだけでつながります。API キーを発行した覚えも、アプリを登録した覚えもありません。最近よく見るこの認証です。 でも、考えてみると不思議です。Claude アプリに入力したのは MCP サーバーの URL だけなのに、ログイン画面の場所はどうやって分かったのでしょうか。逆に、接続先のサービスは Claude というアプリのことをどうやって知ったのでしょう。同意画面に出ている「Claude」は、本当に Claude 自分の使っている Claude アプリなのでしょうか。 MCP の OAuth には、まず次の 2つの「相手を知る」仕組みがあります。 認可サーバーを見つける:MCP クライアントは、どこにログインしに行けばいいのか? → OAuth discovery MCP クライアントを識別する:認可サーバーは、接続してきたアプリが誰なのか? → 事前登録・DCR・CIMD この順番で見ると、MCP サーバーの URL だけで接続できる理由が分かります。最後に Better Auth、Neon、Supabase の対応状況も比較します。 まずは登場人物 MCP クライアント:Claude や ChatGPT、Cursor など、MCP サーバーにつなぐ AI アプリ。OAuth では「クライアント」 MCP サーバー:ツールやデータを提供する側。OAuth ではアクセスを保護される「リソースサーバー」 認可サーバー:ユーザーのログインと同意を受け付け、MCP サーバー用のアクセストークン(通行証)を発行する側 MCP サーバーと認可サーバーは同じサービス内にあっても、別のドメインや別のサービスにあっても構いません。だから、MCP サーバーの URL を知っているだけでは、認可サーバーの場所が分からない場合があります。 1. 認可サーバーの場所をどう伝えるか:OAuth discovery discovery がなかったらどうなる? 従来の OAuth 連携では、アプリの開発者が接続先のサービスをあらかじめ知っていて、認可エンドポイントやトークンエンドポイントを設定しておくのが一般的でした。 例えば、次の情報を手動で用意します。 MCP サーバー: mcp.example.com 認可エンドポイント: auth.example.com トークンエンドポイント: auth.example.com これは接続先が決まっているアプリなら問題ありません。しかし MCP では、MCP クライアント に新しい MCP サーバーの URL を貼るたびに、そのサーバーがどの認可サーバーを使うか分かるとは限りません。認可先が別のドメインなら、MCP サーバーの URL から推測することもできません。 実は 2025年3月版の MCP 仕様には、メタデータで発見できない場合、MCP サーバーと同じオリジンの /authorize、/token、/register を試すフォールバックがありました。例えば mcp.example.com なら mcp.example.com を試す方式です。 ただし、このフォールバックは認可サーバーが別の場所にあれば使えません。 そこで 2025年6月版以降の MCP 仕様では、MCP サーバーが認可サーバーの場所を公開する仕組みを使います。これが OAuth discovery です。 discovery がある場合:2段階で見つける ポイントは、見つけるメタデータが 2種類あることです。 誰が公開する? 仕様 クライアントに教えること MCP サーバー RFC 9728:Protected Resource Metadata どの認可サーバーを使うか 認可サーバー RFC 8414:Authorization Server Metadata(または OIDC Discovery) ログイン先・トークン発行先・対応する登録方式 例えば mcp.example.com にアクセスしてトークンを持っていないと、MCP サーバーは次のように応答します。 HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="mcp.example.com" クライアントがその URL にアクセスすると、次のような Protected Resource Metadata を受け取ります。 { "resource": "mcp.example.com", "authorization_servers": ["auth.example.com"] } これで「認可サーバーは auth.example.com にある」と分かります。続いて認可サーバーの /.well-known/oauth-authorization-server などを取得すると、例えば次の情報が分かります。 { "issuer": "auth.example.com", "authorization_endpoint": "auth.example.com", "token_endpoint": "auth.example.com", "client_id_metadata_document_supported": true } authorization_endpoint がブラウザでログイン・同意を受ける先、token_endpoint が認可コードをトークンに交換する先です。最後の client_id_metadata_document_supported は、後半で説明する CIMD に対応していることを示します。 図の alt の部分がポイントです。WWW-Authenticate にメタデータの URL がなくても、discovery がないとは限りません。ヘッダー経由か well-known URL 経由かの違いで、どちらも RFC 9728 の discovery です。MCP サーバー側はどちらか一方を実装すればよく、ヘッダーで場所を伝えるなら、メタデータの URL は標準の well-known パスでなくても構いません。 一方、RFC 9728 のメタデータ自体がない場合は別です。手動で認可先を設定する独自の接続方法は考えられますが、MCP の OAuth 仕様には適合しません。RFC 9728 の実装は 2025年6月版から必須で、2026年7月版(現行)でも変わっていません。 ここまでで「どこへログインしに行くか」が分かりました。しかし、認可サーバーにはもう1つ知りたいことがあります。「あなたはどのアプリですか?」です。 2. 接続するアプリをどう識別するか:事前登録・DCR・CIMD OAuth では、アプリを識別する ID を client_id と呼びます。認可サーバーは、ログインの要求に付いてくる client_id を見て、アプリの情報やログイン後の戻り先を確認します。 この client_id を使えるようにする仕組みが3つあります。 方式 client_id を用意するのは たとえるなら 事前登録 管理者が事前に登録し、認可サーバーが発行 受付の名簿に前もって名前を載せてもらう DCR(Dynamic Client Registration) アプリが要求し、認可サーバーが発行 受付で自分で用紙を書き、番号札をもらう CIMD(Client ID Metadata Document) アプリ自身が公開する HTTPS URL 公式サイト上の名刺を渡し、受付が見に行く 事前登録:人が登録してから使う 従来の OAuth では、管理者が認可サーバーの管理画面でアプリを登録し、発行された client_id をアプリの設定に書き込みます。 利点:登録済みの相手を管理しやすい。既知のアプリ同士ならシンプル 課題:アプリと認可サーバーの組み合わせごとに事前の登録作業が必要 Web アプリが数個なら手作業で回ります。しかし MCP では、たくさんの AI アプリがたくさんのサーバーにつながるので、登録作業が掛け算で増えます。 DCR:アプリが自分で登録する DCR は、アプリが認可サーバーの登録エンドポイントに「私はこういう名前で、ログイン後はここに戻してほしい」と送り、その場で client_id を受け取る方式です。 解決したこと:人手による事前登録なしで「MCP サーバーの URL を貼るだけ」の体験を実現しやすくなった 課題: アプリ名は原則として自己申告で、追加の審査がなければ誰でも「Claude」を名乗れる 再登録のたびに client_id が増える運用では、同じアプリの登録が溜まる client_id は認可サーバーごとに別物になる CIMD:自己紹介ファイルの URL を ID にする CIMD は、アプリが自分の HTTPS サイトに自己紹介の JSON を置き、その URL 自体を client_id として使う方式です。認可サーバーがその URL を取りに行って内容を確かめるため、接続のたびに登録 API から ID を発行してもらう必要がありません。 Claude も、実際に自己紹介ファイルを公開しています。 Claude(claude.ai、デスクトップ版などの MCP 接続向け):claude.ai 以下は主要な項目の抜粋です。 { "client_id": "claude.ai", "client_name": "Claude", "client_uri": "claude.ai", "redirect_uris": ["claude.ai"], "token_endpoint_auth_method": "none" } client_id が自分自身の URL、client_name が表示名、redirect_uris がログイン後の戻り先です。token_endpoint_auth_method: "none" は、このクライアントがトークンエンドポイントでクライアントシークレットによる認証をしないことを示します。Claude Code は 別の自己紹介ファイル を使い、戻り先を localhost にして利用者の PC に戻す構成です。 解決したこと: 身元をアプリが管理する HTTPS ドメインと結びつけられる。誰でも登録時に「Claude」と書ける DCR より、表示名の出所を確認しやすい 登録 API を呼ばずに、同じ client_id を複数の対応する認可サーバーで再利用できる 接続のたびに新しい ID を発行する必要がない 注意点: URL の取得先を悪用した SSRF(内部ネットワークへの不正アクセス)への対策が必要 認可サーバーが取得したメタデータをキャッシュ・保存する場合はある。「何も保存しない」という意味ではない ドメインを確認しても、動いているプログラムそのものの身元まで完全に証明できるわけではない。特にローカルアプリの戻り先には追加の注意が必要 MCP の推奨は DCR から CIMD へ 時期 MCP 仕様での扱い 2025年3月 認可の仕組みが導入され、DCR を推奨 2025年6月 認可サーバーの発見に RFC 9728 が必須になり、旧フォールバックは廃止。登録方式は引き続き DCR を推奨 2025年11月 CIMD を推奨し、DCR は任意対応に 2026年7月(現行) DCR を非推奨(deprecated)と明記。互換性のため残る 2026-07-28 版の MCP 仕様は、次の順番で選ぶよう勧めています。 すでに登録情報を持っていれば、事前登録を使う 認可サーバーが client_id_metadata_document_supported: true を公開していれば、CIMD を使う それが使えず、registration_endpoint があれば、DCR を使う どれも使えなければ、ユーザーにクライアント情報の入力を求める ここで client_id_metadata_document_supported や registration_endpoint を知る手段が、先ほどの 認可サーバーの discovery(RFC 8414) です。つまり discovery と CIMD は別の仕組みですが、接続フローの中ではつながっています。 3. URL を貼ってから「許可する」まで 2つの仕組みを合わせると、最初の疑問に答えられます。 ユーザーが Claude (MCP クライアント) に MCP サーバーの URL を設定する Claude が MCP サーバーの Protected Resource Metadata(RFC 9728) から認可サーバーを知る Claude が認可サーバーの メタデータ(RFC 8414 など) からログイン先と利用可能な登録方式を知る Claude が client_id を用意する(事前登録・CIMD・DCR のいずれか) ブラウザにログイン・同意画面が開く ユーザーが「許可する」を押すと、Claude が認可コードを受け取り、PKCE を使ってトークンに交換する。このとき resource パラメータで「どの MCP サーバー用のトークンか」も指定する Claude が アクセストークン を付けて MCP サーバーのツールを呼ぶ 利用者には「URL を貼る → ログインする → 許可する」だけに見えますが、裏側では 認可先の発見 と クライアントの識別 が別々に進んでいます。 観点 OAuth discovery 事前登録・DCR・CIMD 解決する疑問 どこでログインする? どのアプリが接続する? 主に情報を示す側 MCP サーバーと認可サーバー MCP クライアントと認可サーバー 重要なメタデータ authorization_servers、authorization_endpoint など client_id、redirect_uris など 2026年7月版 MCP での扱い RFC 9728 による discovery が必須(2025年6月版から) CIMD を推奨、DCR は非推奨 4. Better Auth・Neon・Supabase の対応状況 Better Auth:discovery と CIMD の両方に対応 Better Auth の MCP プラグインは、OAuth の認可サーバー情報と RFC 9728 の Protected Resource Metadata を公開できます。さらに CIMD プラグインを組み合わせることで、URL 形式の client_id を受け入れられます。CIMD は 1.7(2026年8月)で追加された機能で、プラグインを入れたときだけ discovery に client_id_metadata_document_supported が載ります。 CIMD プラグイン自体は IETF draft の新しい版(draft-02)に準拠しており、MCP 2026年7月版が参照する draft-00 の要件(client_name と redirect_uris の必須化)は metadataProfile: "mcp-2026-07-28" を指定して上乗せします。DCR は明示的に有効化しない限り無効です。 Neon:公式 MCP サーバーは DCR のまま、Neon Auth は CIMD が既定 Neon の公式 MCP サーバー(mcp.neon.tech OAuth discovery に対応していますが、クライアント登録は DCR です。CIMD を受け入れる PR #380 が2026年10月に出されていますが、本記事執筆時点では未マージです。 一方、Neon Auth(Managed Better Auth)の OAuth provider プラグインで MCP サーバーの URL を設定すると、CIMD が既定で有効になり、DCR は「legacy」扱いで既定オフになります。2026年10月にこの既定が切り替わったため、それ以前に設定したブランチで DCR に依存していた場合は、明示的にオンにし直す必要があります。管理用の API と後述の SDK はベータ扱いですが、プラグイン自体はすべての Managed Better Auth プロジェクトで使えます。Neon 公式 MCP サーバーの仕組みと、Neon Auth を使った自作 MCP サーバーの実装は分けて考える必要があります。 2026年10月には、Neon Auth を使うアプリや MCP サーバー向けの @neon/oauth(ベータ) も加わりました。MCP サーバー用の RFC 9728 メタデータ公開とトークン検証に加えて、OAuth クライアントが CIMD で名乗るための自己紹介ファイルの生成にも対応しています。 Supabase:discovery を簡単に実装できるが、クライアント登録は DCR Supabase は2026年10月22日に Give your Supabase app its own MCP server を公開しました。新しい MCP Server block を使うと、Edge Functions に MCP サーバーを追加できます。 @supabase/server の withOAuthProtectedResource() が OAuth discovery(RFC 9728) を担当し、認可サーバーの場所を示すメタデータや、未認証時の 401 / WWW-Authenticate 応答を提供します。このメタデータは標準の well-known パスではなく関数配下の URL で配信され、場所は WWW-Authenticate ヘッダーで伝えます。前半で触れた「ヘッダー経由の discovery」の実例です。withSupabase({ auth: 'user' }) はトークンを検証し、ツールをログインユーザーの権限(RLS)で動かします。OAuth Consent block も用意されています。 ただし、discovery に対応したことは CIMD に対応したことを意味しません。外部 MCP クライアント向けの MCP Server block のガイドでは、今も DCR を有効にするよう案内されています。 [auth.oauth_server] enabled = true authorization_url_path = "/oauth/consent" allow_dynamic_registration = true CIMD 対応を求める Issue #2850 は2026年10月23日時点で未解決です。Claude のように CIMD を優先するクライアントも、Supabase Auth を認可サーバーとする接続では DCR に切り替えることになります。 今回の Supabase の発表は、MCP サーバー側の認証と discovery の実装を楽にしたという進歩です。一方、接続してくるアプリの識別を DCR から CIMD に移す課題は残っています。 3つを並べると discovery(RFC 9728) CIMD DCR Better Auth 対応 プラグインで対応 明示的に有効化 Neon Auth 対応 既定で有効 legacy、既定オフ Supabase Auth 対応 未対応 有効化を案内 まとめ MCP サーバーの URL を貼るだけでつながる体験は、2つの問いを順番に解決することで成り立っています。 認可サーバーはどこ? → OAuth discovery(RFC 9728 と RFC 8414 / OIDC Discovery) 接続してきたアプリは誰? → 事前登録・DCR・CIMD OAuth discovery がなければ、認可先を人が設定したり固定パスから推測したりする必要がありました。MCP の OAuth 仕様は 2025年6月版からその発見方法を標準化しています。そして、認可先が分かった後のクライアント識別も、自己申告の DCR から、HTTPS ドメインに結びつく CIMD へ移りつつあります。Better Auth と Neon Auth はすでに CIMD を推奨または既定にし、Supabase は DCR のままという違いが、その移行の途中経過を表しています。 次に同意画面で「Claude」と表示されたら、どこにログインするのか、そしてその「Claude」という名前をどう確かめたのか。この2つを思い出すと、OAuth の仕組みが少し違って見えるはずです。 参考 MCP 仕様 2026-07-28:Authorization MCP 仕様 2026-07-28:Authorization Server Discovery MCP 仕様 2026-07-28:Client Registration MCP 仕様 2025-11-25:Authorization MCP 仕様 2025-06-18:Authorization(RFC 9728 必須化) MCP 仕様 2025-03-26:Authorization(旧フォールバック) IETF draft:OAuth Client ID Metadata Document Better Auth:MCP Better Auth:CIMD Neon:MCP Server Neon:mcp-server-neon PR #380(CIMD 対応提案) Neon Auth:OAuth Provider プラグイン Neon:@neon/oauth PR #869 Supabase:Give your Supabase app its own MCP server Supabase Library:MCP Server block Supabase Auth:CIMD 対応 Issue #2850