コンテンツにスキップ

Auth0 を authorization server にする

このレシピは Auth0Kozou の OAuth resource-server モードの authorization server として設定します。Auth0 は摩擦の少ない SaaS の選択肢です。RFC 8707 の resource= パラメータをネイティブに解釈するため、audience 側には mapper が一切不要です — 手間はその分クライアント登録側に移ります。

identifier があなたの resource URI と完全一致する API を作成します (Applications → APIs → Create API)。例: https://mcp.example.com/mcp

ここが、Keycloak では mapper が要るのに Auth0 では無償で手に入る部分です。MCP クライアントは認可・token リクエストに resource=<resource URI> を送り、Auth0 はそれを audience として扱い (テナント既定は resource_parameter_profile: "audience")、追加設定なしで正しい aud の JWT を発行します。Kozou の厳密な audience 検査はそのまま通ります。

API 側の設定:

  • permission (scope) を 2 つ追加します: mcp:describemcp:execute
  • Allow Offline Access を有効にします (refresh トークンを発行できるように)。
  • consent 画面の文言は scope の名前から自動生成されます — description フィールドはそこに表示されないため、ユーザーに読まれてよい scope 名を選んでください。

Step 2: post-login Action で role claim を出す

Section titled “Step 2: post-login Action で role claim を出す”

Auth0 は user metadata を自動ではアクセストークンに載せません。小さな post-login Action (Actions → Library → Build custom) が担います:

exports.onExecutePostLogin = async (event, api) => {
const role = event.user.app_metadata?.app_role;
if (role) {
api.accessToken.setCustomClaim('role', role);
}
};

これを Login フローに取り付けます。知っておくべきことが 2 つ:

  • OIDC 標準の claim 名を使わないでください。 Auth0 は preferred_username のような名前への setCustomClaim黙って落とします — エラーは出ず、claim がただ現れません。素の独自名 (role、Kozou の既定) は動きます。namespace 付き URL も可です。
  • この Action は app_role がないとき、意図的に何も設定しません。 その場合トークンは role claim なしで Kozou に到達して拒否されます — 誰もまだ認可していないユーザーに対するまさに意図された挙動です。fallback role で「修正」しないでください。認証済みの全ユーザーにデータベースアクセスを与えることになります。

Step 3: ユーザーにロールを割り当てる

Section titled “Step 3: ユーザーにロールを割り当てる”

各ユーザーの app_metadata.app_role に、引き受けるべき PostgreSQL ロールを設定します (User Management → Users → App Metadata、または Management API):

{ "app_metadata": { "app_role": "app_viewer" } }

これがロール割り当てのあるべき姿 — 明示的な管理者アクションです。federated ログイン (Auth0 の connection 経由の Google Workspace 等の上流 IdP) では二重に重要です: 初回の federated ユーザーは空の app_metadata で作成されるため、認証は通っても、管理者がロールを割り当てるまで Kozou には拒否されます。また claim はトークン発行時に焼き込まれますapp_metadata を変更しても発行済みトークンは変わりません。反映には新しいトークン (再認可か refresh) が必要です。

上流 IdP をブリッジする場合の役割分担は: 上流ディレクトリが認証を担い (アカウント・MFA・条件付きアクセス)、認可の素材 (app_role) は Auth0 側が持つ、です。上流には何も足す必要がありません。

Auth0 が Keycloak と最も違うのはここです。Auth0 は dynamic client registration に対応します (テナントのフラグ 1 つ) が、動的登録された third-party クライアントは、最初の認可リクエストが通る前に、あなたの API へのアクセスを追加で承認されている必要があります — Management API で作る、subject_type: "user" のクライアントごとの client grant です。これがないと /authorizeinvalid_request (「Client … is not authorized to access resource server」) で失敗し、エンドユーザーには Auth0 の汎用エラー「Oops!, something went wrong」として見えます。これは AS 側の承認であり、Kozou 側の応答では回避できません。

この壁が、クライアント別の実践レシピを決めます:

  • claude.ai — クライアントの事前登録 (実質必須)。 claude.ai は登録後ほぼ即座に認可へ進みます。grant 不足で認可が失敗するとそのクライアントを捨て、次の試行で新規に登録し直すため、後追いの grant 付与は間に合いません。代わりに: Auth0 で Regular Web Application を作成し、callback URL を https://claude.ai/api/mcp/auth_callback に設定し、API へのアクセスを一度承認した上で、その client ID と secret を claude.ai のコネクタ設定に貼り付けます (コネクタ設定にはまさにこの状況のための OAuth client ID / secret 欄があります)。
  • ChatGPT — grant の自動化、またはここでも事前登録。 ChatGPT は DCR にフォールバックし、接続試行のたびに新規クライアントを登録します (いずれも名前は「ChatGPT」)。DCR を開けたままにするなら、それらを検知して承認する Management API の自動化が必要です。事前登録クライアントならこの賽の河原を回避できます。
  • Claude Code — DCR で動きます。 dynamic registration を有効にし (テナント設定enable_dynamic_client_registration)、third-party クライアントの前提を 2 つ押さえてください: ユーザーがサインインに使う connection の domain レベルへの昇格 (is_domain_connection: true) と、登録されたクライアントへの上記 client grant (Claude Code はサーバー URL ごとに登録をキャッシュするため、開発者ごとに 1 回の付与で済みます)。

クライアントの衛生管理も計画に入れてください: 動的登録クライアントは蓄積し (テナントのアプリケーション数上限を消費します)、クライアント側がまだキャッシュしているものを削除するとエンドユーザーには原因不明のエラーになります — 運用ノート参照。

server:
mcp:
http:
auth:
resource: https://mcp.example.com/mcp
authorizationServers:
- https://your-tenant.auth0.com/
jwt:
jwksUri: https://your-tenant.auth0.com/.well-known/jwks.json
algorithms: [RS256]
roleClaim: role
allowedRoles: [app_viewer, app_editor]
extraScopesSupported: [offline_access]

末尾スラッシュに注意してください: Auth0 の issuer は https://your-tenant.auth0.com/ (スラッシュ付き) で、Kozou は authorizationServers の値を期待 iss として完全一致で照合します。カスタムドメインも同様です — テナントに設定済みなら、そのカスタムドメインを issuer として使ってください。

  1. curl https://mcp.example.com/.well-known/oauth-protected-resourceauthorization_servers にあなたのテナントが現れるはずです。
  2. 実クライアントで接続します (Claude Code: claude mcp add --transport http kozou https://mcp.example.com/mcp/mcp → 認証)。Universal Login と consent 画面が表示され、consent にはあなたの scope 名が並びます。
  3. access token をデコードします: aud = resource URI (mapper なしで、です — それが RFC 8707 経路が機能している証拠)、iss = 末尾スラッシュ付きのテナント URL、role = app_metadata 由来の PostgreSQL ロール。
  4. 失格系の確認: app_metadata.app_role のないユーザーは拒否されること。未承認の動的登録クライアントからの認可は Auth0 側で失敗すること (それが client-grant の壁であり、Kozou のエラーではありません)。