Skip to content

mappy_mcp_oauth_*(MCP OAuth 2.1 認可サーバー DB)

項目内容
ステータス🟡 設計中
関連案件#13 Mappy MCP サーバー新設
親ドキュメントMCP サーバー 全体像
関連設計認証・テナンシ / DB 設計(監査ログ)

ER 図

概要

MCP 用の OAuth 2.1 認可サーバー で必要となる永続データを管理する。

Mappy 既存 OAuth は流用不可

DB 確認結果より、Mappy には Laravel Passport のテーブルが存在しないため流用不可。本テーブル群を新規構築する。

設計方針

方針内容
JWT + Opaque の使い分けaccess_token は JWT(DB 不要、jti のみ失効管理)、refresh_token は Opaque(DB 必須)
refresh_token rotate使い回しを防ぐため、リフレッシュごとに新トークンへローテーション
family_idrotate のチェーンを追跡。盗難検知時に family 全体を一括失効
クライアント認証パブリッククライアント(Claude/ChatGPT)は PKCE のみ、シークレットは optional
動的クライアント登録RFC 7591 対応のため、自動登録もサポート
同意管理scope ごとのユーザー同意を記録、UI から取り消し可能
権限の最小化scope は明示同意のもののみ access_token に乗せる

テーブル定義

1. mappy_mcp_oauth_clients(OAuth クライアント)

Noカラム名(論理)カラム名(物理)データ型NULLキー説明
1IDidbigint unsignedNOPK自動採番
2クライアント IDclient_idvarchar(64)NOUK公開可能な識別子(UUIDv4 等)
3クライアントシークレットハッシュclient_secret_hashvarchar(255)YES-bcrypt ハッシュ。パブリッククライアントは NULL
4クライアント名client_namevarchar(128)NO-"Claude Desktop", "ChatGPT Connector" 等
5クライアント URIclient_urivarchar(255)YES-クライアントサービスの URL
6ロゴ URIlogo_urivarchar(255)YES-同意画面に表示するロゴ
7ポリシー URIpolicy_urivarchar(255)YES-プライバシーポリシー
8利用規約 URItos_urivarchar(255)YES-利用規約
9リダイレクト URI 一覧redirect_urisjsonNO-許可された callback URL の配列
10許可 grant_typesgrant_typesjsonNO-["authorization_code", "refresh_token"]
11許可スコープscopesjsonNO-このクライアントが要求可能な scope 配列
12トークン認証方法token_endpoint_auth_methodvarchar(64)NO-client_secret_basic / client_secret_post / none
13クライアント種別client_typevarchar(16)NO-confidential / public
14登録方法registration_methodvarchar(16)NO-manual / dynamic
15登録アクセストークンregistration_access_token_hashvarchar(255)YES-RFC 7592 用、動的登録時に発行
16有効フラグis_enabledtinyint(1)NO-1: 有効 / 0: 停止
17信頼レベルtrust_leveltinyintNO-0: untrusted / 1: trusted(公式 SDK 等)
18最終使用日時last_used_attimestampYES-最終トークン発行日時
19作成日時created_attimestampNO-登録日時
20更新日時updated_attimestampNO-最終更新日時
21削除日時deleted_attimestampYES-論理削除

インデックス

名前カラム
uk_client_idclient_id UNIQUE
idx_clients_enabled(is_enabled, created_at)
idx_clients_trusttrust_level

初期データ(マニュアル登録分)

公式クライアントは事前登録する:

client_nameclient_typeredirect_uristrust_level
Claude Desktoppublic["https://claude.ai/api/mcp/auth_callback"]1
Claude Codepublic["http://localhost:*/callback"](パターン許可)1
ChatGPT Connectorpublic["https://chat.openai.com/oauth/callback"]1

2. mappy_mcp_oauth_consents(ユーザー同意)

ユーザーがクライアントに与えた scope の記録。同じクライアント+ユーザーで scope を変更した場合は新規行を追加し、旧行を revoked_at で失効。

Noカラム名データ型NULLキー説明
1idbigint unsignedNOPK自動採番
2client_idvarchar(64)NOIDX, FKクライアント
3user_idbigint unsignedNOIDX, FKmappy_users.id
4user_kindvarchar(16)NO-mappy / admin
5admin_idbigint unsignedYESFKadmins.id(user_kind=admin の場合)
6scope_grantedtextNO-スペース区切り scope 文字列
7granted_attimestampNO-同意日時
8granted_ipvarchar(45)YES-同意時の IP
9revoked_attimestampYES-取り消し日時
10revoked_reasonvarchar(64)YES-user_action / admin_action / token_compromise / expired

インデックス

名前カラム
idx_consents_client_user(client_id, user_id, revoked_at)
idx_consents_user_active(user_id, revoked_at)

3. mappy_mcp_oauth_authorization_codes(短期コード)

OAuth 認可コード。発行から 60 秒で失効、1 回使用で消費。 Redis 優先だが、永続化要件があれば DB も併用。

Noカラム名データ型NULLキー説明
1idbigint unsignedNOPK自動採番
2code_hashvarchar(255)NOUKSHA-256(code)
3client_idvarchar(64)NOIDX, FK
4user_idbigint unsignedNOFK
5user_kindvarchar(16)NO-
6scopetextNO-要求 scope
7redirect_urivarchar(255)NO-認可時の redirect_uri
8code_challengevarchar(255)NO-PKCE
9code_challenge_methodvarchar(16)NO-"S256"
10noncevarchar(64)YES-OpenID 用(将来)
11issued_attimestampNO-
12expires_attimestampNOIDXissued_at + 60 sec
13consumed_attimestampYES-トークン交換時刻

Redis 優先理由

authorization_code は短命(60 秒)かつ書込頻度が高いため、Redis(INCR + TTL)で実装する方が効率的。 監査用の永続記録は mappy_mcp_audit_logs 側で行う。 DB 化は「監査要件が高い」「永続化監査が必要」な場合のみ。

4. mappy_mcp_oauth_refresh_tokens(リフレッシュトークン)

長期トークン、rotate 必須。

Noカラム名データ型NULLキー説明
1idbigint unsignedNOPK自動採番
2token_hashvarchar(255)NOUKSHA-256(token)
3client_idvarchar(64)NOIDX, FK
4user_idbigint unsignedNOIDX, FK
5user_kindvarchar(16)NO-
6admin_idbigint unsignedYESFK
7scopetextNO-
8family_idvarchar(64)NOIDXrotate チェーン識別子。盗難検知用
9parent_token_hashvarchar(255)YES-直前トークンの hash(チェーン記録)
10issued_attimestampNO-
11expires_attimestampNOIDXissued_at + 30 days
12last_used_attimestampYES-リフレッシュ時に更新
13rotated_attimestampYES-rotate で失効した時刻
14revoked_attimestampYES-失効日時
15revoke_reasonvarchar(64)YES-user_revoke / rotation / family_compromise / expiry
16request_ipvarchar(45)YES-発行時 IP
17user_agentvarchar(255)YES-
18created_attimestampNO-

インデックス

名前カラム
uk_token_hashtoken_hash UNIQUE
idx_refresh_client_user(client_id, user_id)
idx_refresh_familyfamily_id
idx_refresh_expiresexpires_at
idx_refresh_active(user_id, revoked_at, expires_at)

family_id による盗難検知

  1. 親トークンが既に rotate 済みなのに使われた → token reuse 検出
  2. 該当 family_id の全トークンを失効
  3. ユーザーに通知 + アラート

OAuth 2.1 ベストプラクティスの中核。

5. mappy_mcp_oauth_access_token_revocations(アクセストークン失効リスト)

access_token は JWT(自己完結型)のため通常 DB チェック不要だが、失効時のために jti を一時保存する。

Noカラム名データ型NULLキー説明
1idbigint unsignedNOPK自動採番
2jtivarchar(64)NOUKJWT ID
3client_idvarchar(64)NOIDX
4user_idbigint unsignedNOIDX
5revoked_attimestampNO-失効日時
6revoke_reasonvarchar(64)NO-
7expires_attimestampNOIDX元 JWT の exp。これを過ぎたら DB から削除可
8created_attimestampNO-

運用

  • access_token 検証時、まず JWT 署名 + exp チェック → OK なら jti が本テーブルに無いか確認
  • expires_at < NOW() の行は日次 DELETE
  • パフォーマンスのため Redis にもキャッシュ(Bloom Filter で否定検索を最適化)

インデックス

名前カラム
uk_jtijti UNIQUE
idx_revocations_expiresexpires_at

トークンライフサイクル(フロー再掲)

クライアント登録パターン

A) マニュアル登録(公式クライアント)

  • DB に直接 INSERT(migration / seeder)
  • trust_level=1
  • registration_method='manual'

B) 動的登録(RFC 7591)

http
POST /oauth/register
Content-Type: application/json

{
  "client_name": "Custom MCP Client",
  "redirect_uris": ["https://example.com/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"]
}

レスポンス:

json
{
  "client_id": "01HVABC...",
  "client_secret": null,
  "registration_access_token": "regtok_...",
  "registration_client_uri": "https://mcp.mappy.example.com/oauth/register/01HVABC..."
}

動的登録の制限

ChatGPT 互換のため動的登録は許可するが、untrusted(trust_level=0)として扱う:

  • レート制限を厳しくする(1 時間 60 リクエスト)
  • 同意画面で「未確認のサードパーティ」と警告表示
  • 1 ユーザーあたり同時に登録できるクライアント数を制限(5 個まで)

容量見積もり

テーブル想定行数(1 年)
mappy_mcp_oauth_clients〜200 行
mappy_mcp_oauth_consents〜10,000 行
mappy_mcp_oauth_refresh_tokens〜200,000 行(rotate 込み)
mappy_mcp_oauth_access_token_revocations〜100,000 行(自動 DELETE)

→ 合計でも 100 MB 未満。容量懸念なし。

DDL(参考)

sql
-- ① OAuth クライアント
CREATE TABLE mappy_mcp_oauth_clients (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  client_id VARCHAR(64) NOT NULL,
  client_secret_hash VARCHAR(255) DEFAULT NULL,
  client_name VARCHAR(128) NOT NULL,
  client_uri VARCHAR(255) DEFAULT NULL,
  logo_uri VARCHAR(255) DEFAULT NULL,
  policy_uri VARCHAR(255) DEFAULT NULL,
  tos_uri VARCHAR(255) DEFAULT NULL,
  redirect_uris JSON NOT NULL,
  grant_types JSON NOT NULL,
  scopes JSON NOT NULL,
  token_endpoint_auth_method VARCHAR(64) NOT NULL DEFAULT 'none',
  client_type VARCHAR(16) NOT NULL DEFAULT 'public',
  registration_method VARCHAR(16) NOT NULL DEFAULT 'manual',
  registration_access_token_hash VARCHAR(255) DEFAULT NULL,
  is_enabled TINYINT(1) NOT NULL DEFAULT 1,
  trust_level TINYINT NOT NULL DEFAULT 0,
  last_used_at TIMESTAMP NULL DEFAULT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  deleted_at TIMESTAMP NULL DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uk_client_id (client_id),
  KEY idx_clients_enabled (is_enabled, created_at),
  KEY idx_clients_trust (trust_level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MCP OAuth クライアント';

-- ② ユーザー同意
CREATE TABLE mappy_mcp_oauth_consents (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  client_id VARCHAR(64) NOT NULL,
  user_id BIGINT UNSIGNED NOT NULL,
  user_kind VARCHAR(16) NOT NULL,
  admin_id BIGINT UNSIGNED DEFAULT NULL,
  scope_granted TEXT NOT NULL,
  granted_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  granted_ip VARCHAR(45) DEFAULT NULL,
  revoked_at TIMESTAMP NULL DEFAULT NULL,
  revoked_reason VARCHAR(64) DEFAULT NULL,
  PRIMARY KEY (id),
  KEY idx_consents_client_user (client_id, user_id, revoked_at),
  KEY idx_consents_user_active (user_id, revoked_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MCP OAuth ユーザー同意';

-- ③ リフレッシュトークン
CREATE TABLE mappy_mcp_oauth_refresh_tokens (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  token_hash VARCHAR(255) NOT NULL,
  client_id VARCHAR(64) NOT NULL,
  user_id BIGINT UNSIGNED NOT NULL,
  user_kind VARCHAR(16) NOT NULL,
  admin_id BIGINT UNSIGNED DEFAULT NULL,
  scope TEXT NOT NULL,
  family_id VARCHAR(64) NOT NULL,
  parent_token_hash VARCHAR(255) DEFAULT NULL,
  issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  expires_at TIMESTAMP NOT NULL,
  last_used_at TIMESTAMP NULL DEFAULT NULL,
  rotated_at TIMESTAMP NULL DEFAULT NULL,
  revoked_at TIMESTAMP NULL DEFAULT NULL,
  revoke_reason VARCHAR(64) DEFAULT NULL,
  request_ip VARCHAR(45) DEFAULT NULL,
  user_agent VARCHAR(255) DEFAULT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_token_hash (token_hash),
  KEY idx_refresh_client_user (client_id, user_id),
  KEY idx_refresh_family (family_id),
  KEY idx_refresh_expires (expires_at),
  KEY idx_refresh_active (user_id, revoked_at, expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MCP OAuth リフレッシュトークン';

-- ④ アクセストークン失効リスト
CREATE TABLE mappy_mcp_oauth_access_token_revocations (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  jti VARCHAR(64) NOT NULL,
  client_id VARCHAR(64) NOT NULL,
  user_id BIGINT UNSIGNED NOT NULL,
  revoked_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  revoke_reason VARCHAR(64) NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uk_jti (jti),
  KEY idx_revocations_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MCP OAuth アクセストークン失効リスト';

メンテナンスジョブ

ジョブ頻度内容
期限切れ refresh_token 削除日次WHERE expires_at < NOW() - INTERVAL 30 DAY を物理削除
revocations の expires_at 経過削除日次WHERE expires_at < NOW() を物理削除
30 日未使用 client の警告月次last_used_at < NOW() - INTERVAL 30 DAY をリスト化
family_compromise の検知ログ集計日次revoke_reason='family_compromise' を集計し管理通知

鍵管理(JWT 署名鍵)

項目内容
アルゴリズムRS256(推奨)または HS256
鍵長RS256: 2048bit 以上
ローテーション周期90 日
保管AWS KMS / Secrets Manager
kid(key id)JWT ヘッダーに含め、複数鍵を並行運用可能に
公開鍵公開/.well-known/jwks.json で配布(リソースサーバー用)

未確定事項(設計レビューで決定)

#項目候補
1JWT 署名アルゴリズムRS256(推奨)/ HS256
2refresh_token TTL30 日 / 14 日
3authorization_code 保管Redis 必須 / DB 併用
4access_token TTL60 分 / 30 分
5family_compromise 時のユーザー通知方法メール / Slack / 管理画面
6動的登録の許可全許可 / Allow list で制限
7trust_level の決定主体自動 / 管理者承認
8クライアント停止時の挙動即時失効 / 既存トークンは expires_at まで有効

関連ドキュメント