Ory Hydraでweb3ゲームにサードパーティー向けAPI提供を開始した話
運営中のWeb3ゲーム。https://bravefrontierheroes.com/ja に、Ory Hydraで既存のWebゲームサービスに認可の仕組みを導入。ユーザーが自分で必要となるアプリケーションを提供できるような仕組みを作成
新年は動画見ながらワインを飲んでAIをしばきながらピザを作っていたmogettaです。2025年9月ででかい案件が終わって、(これに関してはとても表に出せないほど面白いので聞きたい方は直接お声がけください)今は小さい仕事をぽつぽつ請け負っていたのですがそろそろ本格的な次の仕事を募集しているのでおもろい仕事あるで!って方はお声がけください。
背景
今お手伝いしているWebゲームでユーザーにAPIを公開し、Web3ゲームのデータを、外部のデベロッパーやコミュニティが活用できるようにしたいとという話を聞き、OAuth2を使う側はやったことがあるがプラットフォームとして提供する側はやったことがないな。と思い、一通り調べた結果、Ory Hydraが今回の要件にあっていた。ということで、そこら辺のまとめ。
はじめはβ版公開として、APIキーを発行し、限られたデータを渡す形を検討していたが、すでに公開されている情報とはいえユーザーの情報の閲覧の認可や短い期間にAPIの仕様がコロコロ変わってしまうのは開発者としてやりにくかろう。と判断し、年内中に認可の仕様を実装して組み込んでしまおうということで12月から走り出した。
個人的には本来Web3ではコントラクトがすべて公開されており、公開されたコントラクトを利用したアプリケーション等のエコシステムがどんどん拡張されていくみたいな世界を期待していました。例えばEveOnlineのhttps://zkillboard.com/ やhttps://evemaps.dotlan.net/ Destiny2のhttps://destinyitemmanager.com/ のようなものです。
しかしながら、Web3の資産性だけが過剰に取り沙汰されたり、また通常の開発者ではWeb3の特性が参入障壁になり、簡単にアプリが開発しにくい。という状況が生まれていました。今回の取組はそういった障壁を取り除き、ゲーム参加者が思う、こういうのがあればいいな。を実現できる最初の1歩となります。
また、AI駆動開発がどんどん本格化している今、swaggerなどを公開して、ユーザー自身がテーラーメイドできるような取り組みは今後必要になってくるのかなとも思っております。
というわけで状況等を整理し、以下のような形でAPI提供を実現しました。
今回作成したAPI ドキュメント https://alabaster-gazelle-9ba.notion.site/Brave-Frontier-Heroes-Developer-Documentation-Forge-API-2d8bb46579d180bdafe0c9fb28d1cc83
swagger https://api.bravefrontierheroes.com/swagger/index.html
clientIDとsecretがあればログイン処理を行い、swagger上で動作確認できる仕組みまで整えてあるのでプログラミングに詳しくないAIコーダーなピーポーにもわかりやすく学べてもらえるはず。

課題
- なるべく最速での実装、公開
- すでにある認証の実装はそのまま使う
- 元のバックエンドサーバーの実装は変えない
- コストは最低限
- AIに聞けばすぐアプリを作れる様な情報を公開しておく
解決策
AIと壁うちし、妥当な解決策を検討していくうち、こういうったものは自前実装よりも、すでにデファクトスタンダードとなっているOSSに乗っていくほうが良いだろう。という結論に至る。そこでOry Hydraを利用させていただくことにする。 OAuth2.0 / OpenID Connectの「認可」に特化した Ory Hydra を導入し、標準的なアクセストークン(JWT)によるAPI利用サーバーを用意する。認証は既存の仕組みを利用する。
主要なコンポーネント:
- フロントエンド - React + Vite - RainbowKit + wagmi v2によるウォレット接続
- バックエンド - Go製APIサーバー - Login/Consent Provider - トークンマッピング管理 - APIプロキシ
- Ory Hydra - OAuth2/OIDC認証サーバー - Public API (4444) / Admin API (4445)
- PostgreSQL - Hydraのデータベース - コンテナ内部でのみアクセス可能
- 外部サービス - MCH Plus Auth: ウォレット署名検証・トークン発行 - BFH Game Server: ゲームデータ取得 (gRPC) - AMS: アセット管理システム
苦労ポイント
- cors
- 関係するコンポーネントが多くて細かいミスが増えるので関係図を作っておきましょう。
- client登録時にcorsも設定するように
- バックエンド側の検証時、IP制限かけたら検証に失敗するように
- hydraのpublic key取得ミスによるエラー。terraform等修正
- ゲームサーバー側にほとんど改修をかけずに作るのでAPIサーバー側でゲームサーバー向けのトークン保持等を行っている。
導線
以下は認可処理を開始してから最終的にAPIを叩けるようになるまでの導線となります。簡易的にコードなども記載。
1 認可リクエスト開始
エントリーポイント。ここで今回の認証のセッション生成等を行う
hydraサーバー/oauth2/auth
リクエスト:
GET /oauth2/auth?
client_id=test-game-client
&respcode
&scope=openid profile
&redirect_uri=http://localhost:8080/callback
&state=random_state
渡すもの:
- client_id: OAuth2クライアントID
- response_type: code (Authorization Code Flow)
- scope: openid profile
- redirect_uri: 認証後のコールバックURL
- state: CSRF対策用のランダム文字列
検証:
- Hydraがクライアント登録を確認
- redirect_uriが登録されたURIと一致するか
返却:
- ログインプロバイダーへのリダイレクト(login_challenge付き)

2 Hydra → Backend (Login Provider)
バックエンドサーバーが「認証」(ウォレットの所持者であることの確認)を行うための処理を開始
バックエンド/login?login_challenge=xxx
検証するもの:
// Hydraから login_challenge を取得
loginChallenge := r.URL.Query().Get("login_challenge")
// Hydra Admin APIでログインリクエストの詳細を取得
loginReq, err := hydraClient.GetLoginRequest(loginChallenge)
Hydra Admin APIに問い合わせ:
GET Hydra Admin API/admin/oauth2/auth/requests/login?login_challenge=xxx
返すもの:
- 既にログイン済みの場合(loginReq.Skip == true): Hydraへログイン承認
- 未ログインの場合: セッションを作成してフロントエンドにリダイレクト
3 Backend → Frontend (OAuth Login Page)
ユーザーにログイン処理を促す。
URL: フロントエンド/oauth-login?sessiyyy
渡すもの:
// セッションを作成
sess, err := sessionStore.Create(loginChallenge)
// フロントエンドにリダイレクト
redirectURL := fmt.Sprintf("%s/oauth-login?sessi%s", frontendURL, sess.ID)
http.Redirect(w, r, redirectURL, http.StatusFound)
セッション内容:
- session_id: 一時的なセッションID
- login_challenge: Hydraのログインチャレンジ(内部保持)
4 Frontend → Backend (Auth Init)
URL: http://localhost:3000/api/auth/init
リクエスト: frontend/src/components/OAuthLoginPage.tsx:45
POST /api/auth/init
Content-Type: application/json
{
"session_id": "yyy",
"address": "0x1234...",
"wallet_name": "..."
}
渡すもの:
- session_id: セッションID
- address: ウォレットアドレス
- wallet_name: ウォレット名(MetaMask等)
検証
// セッションが有効か確認
5 Backend:ウォレット署名検証の準備
リクエスト: backend/internal/mchplus/client.go:54
GET /api/authorize?
respcode
&scope=openid profile
&client_id=xxx
&wallet_name=MetaMask
&state=session_id
&redirect_uri=http://localhost:3000/callback
&address=0x1234...
&lang=ja
渡すもの:
- client_id: MCH Plus クライアントID
- address: ウォレットアドレス
- wallet_name: ウォレット名
- state: セッションID(検証用)
- redirect_uri: ダミーのコールバックURI
返すもの:
{
"message": "I am signing my one-time nonce: abc123..."
}
このメッセージをユーザーがウォレットで署名します。
6 Frontend: ウォレット署名
処理:
frontend/src/components/OAuthLoginPage.tsx:64
// wagmiとethers v5を使って署名
const client = await getConnectorClient(config)
const ethersProvider = new providers.Web3Provider(client)
const signer = ethersProvider.getSigner()
const signature = await signer.signMessage(message)
署名対象:
- 5から取得したメッセージ
返すもの:
- 署名(hex文字列)

7 Frontend → Backend (Auth Verify)
URL: バックエンド/api/auth/verify
POST /api/auth/verify
Content-Type: application/json
{
"session_id": "yyy",
"address": "0x1234...",
"signature": "0xabcd...",
"network": "..."
}
渡す
- session_id: セッションID
- address: ウォレットアドレス
- signature: ウォレットで生成した署名
- network: ネットワーク名(home-verse, oasys-mainnet, mainnet)
検証
// セッション検証
sess, err := sessionStore.Get(req.SessionID)
8Backend → Verify Signature
wallet検証
リクエスト
POST /api/login
Content-Type: application/json
{
"address": "0x1234...",
"client_id": "xxx",
"signature": "0xabcd...",
"network": "home-verse",
"redirect_uri": "http://backend:3000/callback",
"lang": "ja",
"state": "session_id"
}
検証
- wallet検証サーバーが署名を検証
- アドレスとメッセージの整合性を確認
- 署名が本当にそのウォレットからのものか検証
返却
{
"code": "auth_code_12345",
"state": "session_id",
"redirect_uri": "backend/callback"
}
9 Backend → wallet検証サーバー (Token Exchange)
POST /api/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=auth_code_12345
&redirect_uri=http://xxxx:xxxx/callback
&client_id=xxx
&client_secret=yyy
渡すもの:
- grant_type: authorization_code
- code: MCH Plusから取得した認可コード
- client_id: MCH Plus クライアントID
- client_secret: MCH Plus クライアントシークレット(重要)
検証するもの:
- 認可コードが有効か
- client_idとclient_secretが正しいか
- redirect_uriが一致するか
返すもの:
{
"access_token": "access_token_xxx",
"token_type": "Bearer",
"expires_in": 604800,
"refresh_token": "mch_refresh_token_yyy",
"id_token": "id_token_zzz"
}
10 Backend: Token Mapping Store に保存
ここはβ版のため、仮の処理を行っていて、正式版ではゲームサーバー側にhydraの検証の責務を負ってもらう予定。
// トークンマッピングストアに保存
tokenMapStore.Set(req.Address, &tokenmap.AuthToken{
AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken,
IDToken: tokenResp.IDToken,
ExpiresAt: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
})
保存内容: backend/internal/tokenmap/store.go:67
- キー: ウォレットアドレス(0x1234...)
- 値: Auth トークン(access_token, refresh_token, id_token, ExpiresAt)
これにより、Hydra subject(ウォレットアドレス)↔ wallet検証 トークンのマッピングが確立されます。
11 Backend → Hydra (Accept Login)
/admin/oauth2/auth/requests/login/accept
PUT /admin/oauth2/auth/requests/login/accept?login_challenge=xxx
{
"subject": "0x1234...",
"remember": true,
"remember_for": 3600
}
渡すもの:
- subject: ウォレットアドレス(ユーザーの一意識別子)
- remember: ログイン状態を記憶するか
- remember_for: 記憶する期間(秒)
返すもの:
{
"redirect_to": "http://hydra/oauth2/auth?..."
}
このURLにリダイレクトすることで、Hydraの同意画面(Consent)に進みます。
Frontend への返却
{
"redirect_to": "http://hydraサーバー/oauth2/auth?..."
}
12: Hydra → Client (Authorization Code発行)

URL: http://3rdパーティーURL/callback?code=hydra_auth_code_123&state=random_state
渡すもの:
- code: Hydra発行の認可コード
- state: 元のリクエストで渡したstate(CSRF検証用)
クライアント側の検証:
- stateが一致するか確認
13 Client → Hydra (Access Token取得)
URL: http://hydraサーバー/oauth2/token
リクエスト:
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=hydra_auth_code_123
&redirect_uri=http://3rdパーティーURL/callback
&client_id=test-game-client
&client_secret=test-secret
渡すもの:
- grant_type: authorization_code
- code: Hydraの認可コード
- redirect_uri: コールバックURI(一致必須)
- client_id: クライアントID
- client_secret: クライアントシークレット
検証するもの:
- 認可コードが有効か
- クライアント認証情報が正しいか
- redirect_uriが一致するか
返すもの:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "...",
"scope": "openid profile"
}
これでクライアントはHydraのJWTトークンを取得しました。
まとめ
Ory HydraというヘッドレスのOauth2プロパイダーを使うことで迅速にAPIを提供する環境を構築することが出来ました。
ブレヒロでは今後、提供したAPIを利用したハッカソンの開催なども予定されており、是非ご参加をお願いします。