1. 実運用におけるAuth Serverとは何か、なぜKeycloakを選ぶのか
まず短い復習から。Auth Server (IdP) とは次のようなサービスです:
- ユーザーにログイン/登録と同意(consent)の画面を提示する;
- OAuth/OIDC トークン(access_token、id_token、refresh_token)を発行する;
- discovery ドキュメントと JWKS キーを公開し、リソースサーバーがそれらのトークンを検証できるようにする。
我々のスタックでは:
- ChatGPT / MCP Jam は OAuth クライアント(public client)として動作;
- あなたの MCP サーバー — Resource Server として;
- Keycloak — Auth Server として。
Keycloak が学習にも実務にも便利な理由:
- オープンソースで、ローカル/Docker で簡単に立ち上げられる;
- エンティティモデルが比較的わかりやすい: realm、clients、users、roles;
- 本質的に、Keycloak で学んだ設定は Auth0/Okta/Cognito にもほぼ 1:1 で移せる。同じ考え方(client、scopes、redirect URIs、PKCE)があるため。
重要な考え方: 私たちが設定するのは全社共通の Keycloakではなく、今回の ChatGPT App 専用の Realm です。これは、MCP クライアント向け認証のための「サンドボックス」のようなものです。
まとめとして、講義の終わりには次のものが手元にあるはずです:
- ChatGPT アプリ専用の Keycloak の realm;
- Authorization Code + PKCE で動作する public client;
- 最小限の scopes と claims;
- そのトークンがあなたの Node 製 MCP サーバー内でどのように扱われるかの理解。
2. MCP の観点から見た Keycloak の基本エンティティ
この先で管理画面で迷わないように、エンティティを整理しておきます。
Realm: 設定とユーザーのスペース
Keycloak の Realm は、ユーザー、クライアント、ポリシーをもつ隔離された空間です。 わかりやすい比喩としては「ビジネスセンターの賃貸オフィス」。各テナントに専用の部屋、従業員リスト、入室ルールがあるイメージです。
学習用や最初の実アプリでは、専用の realm を作るのが妥当です。例えば giftgenius-mcp や mcp-course。これにより:
- 誤って管理画面を壊してしまわないよう、master realm を触らないで済む;
- realm のエクスポート/インポートで、異なる環境(dev / staging / prod)間で設定やユーザーを再利用できる。
Client: アプリケーションの登録(ChatGPT / MCP Jam)
Keycloak の Client は「ユーザー」ではなく、Auth Server にトークンを要求するアプリケーションのことです。 このケースでは、あなたの Next.js バックエンドではなく、まさに MCP クライアント自体、 つまり ChatGPT、MCP Jam、あるいは UI で手動 OAuth フローを実装しているならあなたのウィジェット、が該当します。
クライアントの主な項目:
- client_id — 文字列の識別子;
- タイプ(public / confidential / bearer-only);
- 有効化する OAuth フロー(Standard Flow、Client Credentials など);
- 許可された redirect URI の一覧;
- scopes の一覧と protocol mappers(トークン内の claims)。
ChatGPT/MCP Jam には public client が必要です。その理由は:
- ChatGPT はクライアントとして client_secret を安全に保持できない;
- MCP Jam もデスクトップ/ブラウザ系ツールで、信頼できない環境にあるため。
User: 実際の人間
User は「生身の」ユーザーです。username、パスワード、email、属性、グループ、ロールを持ちます。 Keycloak で誰かがログインすると、その人の sub やその他のデータがトークンに入り、 あなたは MCP サーバー側でそれを検証し、accountId / tenantId にマッピングします。
デモ段階では次で十分です:
- 1〜2人のテストユーザー(例: alice@example.com、bob@example.com);
- 必要なら tenant や plan のような属性を追加して、トークンの claims が tools の動作に影響する例を示す。
3. クライアント種別の選択: public と PKCE、なぜ secret なしなのか
では、ChatGPT/MCP 用に Keycloak のクライアントをどう設定するか、具体的に見ていきます。
Public vs Confidential: なぜ client_secret ではないのか
従来の Web アプリではバックエンドを用意し、そこに client_secret を置き、サーバーが IdP にトークンを取りに行きます。 これは confidential client(秘密を保持できるクライアント)です。
ChatGPT の世界では逆です:
- OAuth クライアントは ChatGPT プラットフォーム自身や MCP Jam のようなユーティリティである;
- そのコードや実行環境をあなたは制御できない;
- あなたが ChatGPT に渡した client_secret は、すぐに漏洩したものと見なすべき。
そのため、ChatGPT/Jam は public clients として、つまり client_secret なしで動作し、 その不足分を PKCE(Proof Key for Code Exchange)で補います。
PKCE を平易に説明すると
PKCE は「セッションごとの使い捨て秘密」のようなものです。目的は、authorization code を傍受して別の場所からトークンに交換されないようにすること。 流れは次のとおりです:
- クライアントがランダムな文字列 code_verifier を生成する。
- それを(通常は SHA-256 で)ハッシュし、code_challenge を得る。
- /authorize へのリダイレクト時に code_challenge と code_challenge_method=S256 を送る。
- ログイン後、ユーザーは code とともに redirect URI に戻る。
- クライアントは POST で /token に、code と元の code_verifier を送る。
- Keycloak は verifier をハッシュし、challenge と比較し、一致すればトークンを発行する。
ここで重要なのは: これらは ChatGPT/MCP Jam が肩代わりしてくれるということです。 私たちは Keycloak のクライアント側で Authorization Code + PKCE(S256)を有効にし、client_secret を要求しないようにするだけでよいのです。
4. MCP シナリオ向けの Keycloak 設定(手順)
つまり、ChatGPT/MCP Jam 用には public client を使い、 Authorization Code Flow と PKCE(S256)、かつ client_secret なし、という方針です。 では、この設定が Keycloak 上でどう見えるかを確認しましょう。
すでに動作している Keycloak(Docker コンテナ、ローカルインストール等)があるとします。 ここでは「どこをクリックするか」ではなく、設定の考え方にフォーカスします。
アプリ用の新しい realm を作成
realm giftgenius-mcp を作りましょう。ここは次のような専用の領域です:
- ChatGPT アプリ用のユーザー;
- ChatGPT/MCP Jam が OAuth を通るためのクライアント;
- 独自のパスワード/トークンポリシー。
実務的なアドバイス: 管理画面のスタッフ認証に使う realm と、 ChatGPT クライアント向けの realm を混在させないこと。安全性と論理的な分離の両面で有利です。
テストユーザーを追加
例えば alice というユーザーを作ります:
- username: alice;
- email: alice@example.com;
- パスワードを設定(簡単のため厳しいポリシーは不要);
- 必要に応じて属性 tenantId=demo-tenant やロール ROLE_PREMIUM を追加。
後で MCP サーバー側でトークンをデコードし、sub、email、tenantId を取り出して、あなたのユーザーモデルに結びつけられます。
MCP Jam / ChatGPT 用の public client を作成
ここが一番おもしろい部分、Client の設定です。
概念的には、パラメータは次のようになります:
- Client ID: giftgenius-mcp-client(名前は任意);
- タイプ: public / Client Authentication は off;
- Standard Flow(Authorization Code)を有効化;
- S256 メソッドの PKCE を有効化;
- redirect URI を設定;
- 必要な scopes を設定(openid + カスタム例 mcp:tools)。
Standard Flow と PKCE を有効化
概念レベルでは:
- Authorization Code Flow(しばしば「Standard Flow Enabled」と表記)を有効化;
- PKCE セクションで pkceRequired=true を設定し、通常は code_challenge_method=S256 を明示。
なぜ S256 か: OAuth 2.1 の最新ドキュメントや OpenAI/Model Context Protocol の推奨では S256 が安全な方法としてサポートされており、plain 方式の PKCE は安全でないと見なされます。
Redirect URI — 最も壊れやすいポイント
Redirect URI は、クライアントが使用するものと1文字たりとも違ってはいけません。 そうでないと、認可段階で invalid_redirect_uri エラーになります。
このコースで想定する典型的なクライアントは2つあります:
- MCP Jam/Inspector(デバッグ用)。通常は http://localhost:PORT/... で動きます。ローカルでは次のような redirect を許可するのが妥当です:
- http://localhost:5173/* または Jam が使う具体的なパス。
- ChatGPT / Apps SDK(本番)。ここでは redirect URI はプラットフォーム側で決まります。 実際の統合では OpenAI の最新ドキュメントを参照し、ChatGPT が callback として使う必要な URL を登録します。
講義の文脈で重要なのは理解そのものです。ChatGPT は任意の redirect を勝手に使えず、Auth Server に登録済みのものと一致していなければなりません。したがって:
- * を使って「どんな URL でも可」にしないこと;
- ローカル開発では localhost 範囲のワイルドカードは許されることもありますが、本番では避けること。
Scopes: 最小限で、しかし十分に
Scopes は、クライアントが要求する権限のミニリストです。
MCP シナリオでは多くの場合、次が必要です:
- openid — OpenID Connect を有効にし、sub、必要に応じて email を含む id_token を得るため;
- カスタム scope(例: mcp:tools)—「MCP ツールへのアクセスが許可されている」ことを示す。
Keycloak では Client Scopes でこれを行います:
- openid を残す;
- 不要ならデフォルトの profile や email などのスコープを外す;
- 新しい scope mcp:tools を追加し、Resource Server 側でツールへのアクセス制御に使う。
これが重要な理由は2つ:
- openid がないと、id_token や標準的な OIDC フィールドの一部を受け取れません。
- カスタム scope がなければ、MCP サーバー側で「このトークンは自分のツールを呼び出してよい」と明確に判断できません。
5. トークン設定: 有効期間、署名、claims
Keycloak がどのようなトークンを発行し、それを MCP シナリオに合わせてどう設定するかを見ていきます。
access token の有効期間
Keycloak の realm 設定には Tokens セクションがあり、次が設定できます:
- Access Token Lifespan;
- Refresh Token Lifespan などのタイムアウト。
ChatGPT App では、短命な access token が重要です:
- 数分〜数時間程度が妥当;
- トークンが期限切れなら MCP サーバーは 401 を返し、ChatGPT は OAuth フローを再実行、必要に応じてユーザーは再ログインします。
考え方は OpenAI の Apps SDK のドキュメントと同じです。短い TTL、トークン更新、そして IdP 側でトークンを失効させることで比較的すぐにユーザーを「ログアウト」させられるようにします。
ChatGPT クライアントに対する refresh token は、重要度が低いか、あるいはセッションを長期化させないよう短い期間で発行されることが多いです。
トークンに含めたい claims
最小限、次が必要です:
- sub — Keycloak におけるユーザーの一意な ID;
- iss — トークンの発行者(issuer);
- aud — どのリソース向けのトークンか(MCP サーバーで使用);
- exp — 失効時刻;
- scope — scopes の一覧。
追加でよく役立つもの:
- email — ユーザーのメールアドレスが必要な場合;
- tenantId のような claim — マルチテナントのシナリオ向け;
- roles — 追加の認可制御に。
Keycloak では Protocol Mappers で設定します:
- email、preferred_username などの標準マッパー;
- ユーザー属性用のカスタムマッパー(user.attribute → claim.name)。
例: email を claim としてトークンに追加するマッパーでは、 user.attribute=email、 claim.name=email を指定します。
MCP サーバー側では、パースした JWT からこれらの claims を取得して:
- sub をあなたの accountId に対応付ける;
- tenantId を使って、そのテナントのデータだけを取得する;
- roles を使って、よりきめ細かな権限制御を行う。
トークン署名と JWKS
Keycloak はデフォルトで非対称アルゴリズム(通常は RS256)で access/id トークンに署名し、 OpenID Discovery ドキュメントの JWKS エンドポイントから公開鍵を提供します。
これは、MCP サーバーが次のことを行えるため重要です:
- トークンから issuer を取り出す;
- /.well-known/openid-configuration から JWKS エンドポイントを見つける;
- 公開鍵を取得して、ローカルでトークン署名を検証する。
この部分は、MCP サーバーを保護されたリソースとして実装する次回の講義で詳しく扱いますが、Keycloak がメタデータを返す理由は今のうちに理解しておくとよいでしょう。
6. Dynamic Client Registration (DCR): それが必要になるのはいつか
この章はやや上級向けです。ここまで、クライアントは管理画面から「手で」設定してきましたが、それだけで App を動かすには十分です。 ただし OAuth には、クライアントが専用のエンドポイントを通じて動的に登録できる仕組みがあります。
ChatGPT と MCP の文脈では、OpenAI はプラットフォームが Dynamic Client Registration を使う可能性を明言しています。 つまり、ChatGPT は discovery ドキュメントの registration_endpoint を通じて、その場で Auth Server に自分自身を登録できます。
Keycloak では概ね次のようになります:
- realm 単位で DCR を有効化する;
- 誰がどの grant types/scopes で新規クライアントを登録できるかのポリシーを設定する。
Authorization Code + PKCE で scope openid mcp:tools を持つ public client を登録する JSON の例:
{
"clientName": "My ChatGPT App",
"redirectUris": ["https://jam.proxy.mcpapps.com/callback"],
"grantTypes": ["authorization_code"],
"responseTypes": ["code"],
"scope": "openid mcp:tools",
"tokenEndpointAuthMethod": "none"
}
ここで tokenEndpointAuthMethod: "none" は、client_secret を持たない public client を意味します。
本講義では、以下を知っていれば十分です:
- クライアントが多い、あるいは短命である場合に DCR は有用;
- ChatGPT はあなたの IdP に対して自動登録を行う可能性がある;
- ただし最初は UI から作成した静的クライアントで問題なく進められる。
7. これが学習用アプリとどう関係するか
学習用の MCP サーバー(例: GiftGenius)を思い出しましょう。これは以下ができます:
- 候補のギフト一覧を返す;
- ユーザーのウィッシュリストを保存する;
- 将来的にはコマース機能にアクセスして注文処理などを行う。
MCP サーバーが公開状態のうちは、相手が誰かを知りません:
- ChatGPT からのリクエストが論理的には「Alice」からか「Bob」からかもしれませんが、MCP サーバーはそれを区別できない;
- プライベートなギフト履歴を見せられない;
- 適切なアカウントから確実に課金することができない。
Keycloak を Auth Server として設定すると状況は変わります:
- ChatGPT は、あなたの MCP リソースの .well-known を見て、そのリソースが保護されておりトークンが必要だと理解する。
- ChatGPT は Authorization Code + PKCE フローでユーザーを Keycloak に送る。
- ユーザーがログインする(テストユーザー alice)。
- ChatGPT は sub、email、mcp:tools などの claims を含む access token を受け取る。
- ChatGPT は Authorization: Bearer <token> を付けて GiftGenius のツールを呼び出す。
- MCP サーバーはトークンを検証し、「なるほど、これは sub=...、tenantId=demo-tenant を持つ Alice だな」と理解して、適切に応答する。
この連携は次回の講義で完成します。MCP サーバーを本当の resource server として、メタデータのエンドポイント、トークン検証、ユーザー紐付けまで実装します。
8. 小さな実装例(スタック: TypeScript + Node)
ここで示すのは「唯一の正解」ではなく、典型的な Node/TypeScript スタックでどう見えるかの参考例です。 今は Keycloak のクリック操作に集中しているなら、ざっと目を通し、MCP サーバーを接続する段になったら戻ってきてください。
Keycloak の設定自体は UI か Admin REST API で行うのが一般的ですが、 その周辺のコードを少しだけ示して、MCP サーバー側で実際にどう使うかのイメージを掴んでおきましょう。
前提: すでに公式 SDK をベースにした Node.js 製の MCP サーバー(TypeScript)があるとします。
認可設定(issuer と audience)
小さなモジュール authConfig.ts を作ります:
// authConfig.ts
export const authConfig = {
issuer: 'https://auth.my-company.com/realms/giftgenius-mcp',
audience: 'https://mcp.my-company.com', // あなたのMCPサーバーのURL
requiredScopes: ['mcp:tools'], // トークンに最低限含まれていてほしいもの
};
ここで issuer は Keycloak の realm の URL、audience はリソースの識別子です(トークンと MCP の設定でも利用します)。
JWKS を使った基本的な JWT 検証
実際には jsonwebtoken + jwks-rsa のようなライブラリや、MCP SDK のユーティリティを使うことが多いでしょう。 最小の雛形は次のようになります:
// verifyToken.ts
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import { authConfig } from './authConfig';
const client = jwksClient({
jwksUri: `${authConfig.issuer}/protocol/openid-connect/certs`,
});
function getKey(header: any, callback: any) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key?.getPublicKey();
callback(err, signingKey);
});
}
export function verifyAccessToken(token: string): Promise<any> {
return new Promise((resolve, reject) => {
jwt.verify(
token,
getKey,
{
audience: authConfig.audience,
issuer: authConfig.issuer,
},
(err, decoded) => (err ? reject(err) : resolve(decoded)),
);
});
}
もちろん、エラー処理や鍵のキャッシュはより慎重に行うべきですが、ポイントは見てのとおりです。 Keycloak は JWKS キーを公開し、私たちはそれを取得して署名を検証します。
scope の確認と identity の抽出
MCP ツール用のミドルウェアでは、例えば次のようにします:
// authMiddleware.ts
import { verifyAccessToken } from './verifyToken';
import { authConfig } from './authConfig';
export async function requireAuth(bearerToken: string) {
const token = bearerToken.replace(/^Bearer\s+/i, '');
const decoded: any = await verifyAccessToken(token);
const scopes = (decoded.scope as string).split(' ');
const hasScope = authConfig.requiredScopes.every(s => scopes.includes(s));
if (!hasScope) {
throw new Error('Insufficient scope');
}
return {
userId: decoded.sub,
email: decoded.email,
tenantId: decoded.tenantId,
};
}
そして MCP ツールのハンドラーでは、userId と tenantId を使って、対象ユーザーのギフト一覧を読み込むことになります。 ツール自体の実装は前のモジュールで行っていますが、ここでは Keycloak のトークンがバックエンドで理解可能な identity に変換される様子を把握することが重要です。
9. MCP Auth Server として Keycloak を設定する際のよくあるミス
エラー1: client_secret を持つ confidential client を使ってしまう。
慣れで confidential タイプのクライアントを作り、client_secret を MCP/ChatGPT の設定に書こうとしてしまうことがあります。 ChatGPT App のエコシステムではこれは動作すべきではなく、また安全でもありません。ChatGPT は public client で、秘密を保持できません。 正しいのは public client + PKCE です。
エラー2: デフォルトの scopes が広すぎる。
profile や email を含む多数の標準スコープを有効のままにし、そのまま全チャットに配ってしまうのはよくありません。 最小化しましょう。openid と用途に応じた mcp:tools(あるいは少数のアプリ固有スコープ)で、初期バージョンには十分です。 これにより余計なデータ漏洩のリスクが減り、挙動も予測しやすくなります。
エラー3: 誤った redirect URI。
ありがちな例: Keycloak には http://localhost:5173/callback を設定しているが、MCP Jam は http://localhost:5173/ にリダイレクトする(またはその逆)。 結果として invalid_redirect_uri になり、非常に厄介なデバッグに陥ります。 Jam/ChatGPT のドキュメントにある正確な redirect URI を必ず確認し、文字単位で一致させてください。
エラー4: PKCE が無効、あるいは誤ったメソッドで有効化されている。
Keycloak のバージョンによっては「PKCE required」を個別に有効にし、S256 メソッドを指定する必要があります。 これを行わないと、PKCE を期待している ChatGPT/Jam が invalid_request(code_challenge がない等)を受け取る可能性があります。 public クライアントの PKCE 設定を必ず確認してください。
エラー5: トークンの claims が不足または不適切。
必要な scope や protocol mapper が設定されていないため、トークンに sub や email が入っていないことがあります。 その結果、MCP サーバー側でトークンは見えるのに、実ユーザーへのマッピングができません。 対策: 必要なフィールド(最低限 sub。可能なら email/tenantId も)を access/id トークンにマップできているか確認しましょう。
エラー6: access token の TTL が長すぎる。
セキュリティ上、access token を 1日/1週間単位で有効にするのはよくありません。 トークンが漏洩した場合、攻撃者が MCP リソースに長期的にアクセスできてしまいます。 access token は短命(分〜時間)にし、必要に応じて再認可させるのが望ましいです。
エラー7: realm の混同と master の使用。
最初にやりがちなのが、master realm に直接クライアントやユーザーを作ること。 その後、同じ realm に別プロジェクトをさらに追加し、最終的に何がどれか分からなくなります。 アプリ/コースごとに専用の realm を最初から用意しましょう。その方があなたにも、DevOps にも優しい運用になります。
GO TO FULL VERSION