GameVox でログイン
ユーザーが GameVox アカウントで、あなたのウェブサイトやアプリにサインインできるようにします。Discord と同じ形の標準的な OAuth2 認可コードフローで、ボットは不要です。アプリがユーザーのアカウント情報の一部を求め、ユーザーが承認すると、あなたのサーバーがトークンを受け取ります。
用途
- サインインとアカウント連携。ユーザーが誰か(変わることのない GameVox ユーザー ID、ユーザー名、アバター)が分かり、本人の許可があれば確認済みのメールアドレスも取得できます。
- サーバー選択画面とダッシュボード。
guildsを使うと、ユーザーが参加しているサーバーと、設定を管理できるサーバーが分かります。 - ユーザーとして操作するためのものではありません。ユーザートークンでは、メッセージの送信、ボイスへの参加、GameVox 上での変更は一切できません。GameVox 内で何かを行うにはボットが必要です。
始める前に
- developers.gamevox.com でアプリケーションを作成します。OAuth2 タブの クライアント ID が
client_idになります。 - OAuth2 タブで、アプリが使うリダイレクト URI をすべて追加します(最大 10 個)。
https://(開発用ならhttp://localhost/http://127.0.0.1も可)で、#fragmentを含まないものに限ります。送信するredirect_uriは、クエリ文字列も含めていずれかと完全に一致している必要があります。 - クライアントシークレットをコピーします。表示されるのは、アプリの作成時かシークレットのリセット時の一度だけです。すべてのトークンリクエストで必要になるので、サーバー側で保管してください。
エンドポイント
GET https://gamevox.com/oauth2/authorize/
POST https://app-api.gamevox.com/api/oauth2/token
POST https://app-api.gamevox.com/api/oauth2/token/revoke
GET https://app-api.gamevox.com/api/users/@me
GET https://app-api.gamevox.com/api/users/@me/guilds
GET https://app-api.gamevox.com/api/oauth2/@me
PUT https://api.gamevox.com/api/users/@me/applications/{application.id}/role-connection /oauth2/authorize/ の末尾のスラッシュは省略しないでください。トークンとユーザーのエンドポイントは /api/v10/ 配下でも応答するため、パスにバージョンを含めるライブラリもそのまま動きます。ロール接続は app-api ではなく api.gamevox.com にあります。
1. ユーザーを GameVox へ送る
ブラウザを次の認可 URL へリダイレクトします(ポップアップや iframe ではなく、通常のページ遷移で)。
https://gamevox.com/oauth2/authorize/
?response_type=code
&client_id=123456789012345678
&scope=identify%20email
&redirect_uri=https%3A%2F%2Fyourapp.example.com%2Fauth%2Fgamevox
&state=Zx81kq0vT2 | パラメーター | 必須 | 説明 |
|---|---|---|
response_type | はい | 常に code。 |
client_id | はい | アプリの OAuth2 タブにある クライアント ID。 |
scope | はい | スペース区切り(URL エンコードでは %20 または +)。スコープを参照してください。bot は含めないでください。bot があると、この URL はサーバーインストールになります。 |
redirect_uri | はい | 登録済みのリダイレクト URI のいずれかと完全に一致する値。 |
state | いいえ | ユーザーのセッションに紐づく、推測できない値(最大 1024 文字)。そのまま返ってくるので、照合してクロスサイトリクエストフォージェリを防いでください。 |
code_challenge | いいえ | 任意の PKCE チャレンジ。43 から 128 文字。 |
code_challenge_method | いいえ | チャレンジを送る場合は S256。plain は拒否されます。 |
prompt | いいえ | none を指定すると、ユーザーがこれらのスコープすべてについてすでにアプリを承認している場合に同意画面を省略します。新しいスコープがあれば確認を求めます。 |
ユーザーに表示される内容
- gamevox.com 上の同意画面に、アプリの名前、アイコン、説明、サインイン中のアカウント、アプリが参照できる情報を分かりやすく示した一覧、そして移動先のサイトが表示されます。
- サインインしていない場合は、Log in から GameVox ウェブアプリを経由して、同じ画面にそのまま戻ってきます。
- 基本情報タブで利用規約とプライバシーポリシーのリンクを設定すると、ボタンの下に表示されます。
2. コールバックを処理する
ユーザーが Authorize をクリックすると、GameVox はコードを付けてリダイレクト URI へ送ります。
https://yourapp.example.com/auth/gamevox?code=3vR0b...&state=Zx81kq0vT2 代わりに Cancel をクリックした場合:
https://yourapp.example.com/auth/gamevox?error=access_denied
&error_description=The+user+denied+the+request&state=Zx81kq0vT2 stateがこのブラウザに発行したものと一致しなければ、コールバックを拒否してください。- コードの有効期限は 10 分で、使えるのは一度だけです。サーバーからすぐに交換してください。
3. コードをトークンと交換する
サーバーから、フォームエンコードした本文(JSON ではなく application/x-www-form-urlencoded)をトークンエンドポイントへ POST します。
curl -X POST https://app-api.gamevox.com/api/oauth2/token \
-u "123456789012345678:CS.your-client-secret" \
-d grant_type=authorization_code \
-d code=3vR0b... \
-d redirect_uri=https://yourapp.example.com/auth/gamevox {
"access_token": "GVO.mJ4c...",
"token_type": "Bearer",
"expires_in": 604800,
"refresh_token": "GVR.9tQe...",
"scope": "identify email"
} - 認証は上記のとおり HTTP Basic(
client_id:client_secret)で行うか、client_idとclient_secretをフォームのフィールドとして送ります。 redirect_uriはステップ 1 で送ったものと同じ値にしてください。PKCE を使った場合はcode_verifierも追加します。- レスポンスには
Cache-Control: no-storeが付きます。クライアントシークレットやトークンを、ブラウザ側のコードや URL に決して含めないでください。
4. API を呼び出す
アクセストークンを Authorization: Bearer として送ります。GET /users/@me には identify スコープが必要です。
curl https://app-api.gamevox.com/api/users/@me \
-H "Authorization: Bearer GVO.mJ4c..." {
"id": "1557709980780474368",
"gamevox_id": "edf412bc-a7b8-4f2a-a810-8b9eb8c02462",
"username": "alice",
"discriminator": "0",
"global_name": "alice",
"avatar": null,
"gamevox_avatar_url": "https://...",
"email": "alice@example.com",
"verified": true
} idはユーザーの変わることのない GameVox ID です。アカウントのレコードはこれをキーにしてください。ユーザー名は変わることがあります。gamevox_idは、同じユーザーを GameVox ネイティブの UUID 形式で表したものです。emailとverifiedが含まれるのは、emailスコープがあり、かつユーザーがメールアドレスを登録している場合だけです。GameVox は確認済みのアドレスしか保存しないため、emailがある場合verifiedは常にtrueです。avatarは常にnullです(Discord のライブラリはここから Discord CDN のリンクを組み立ててしまうため)。代わりにgamevox_avatar_urlを使ってください。ユーザーに画像がない場合は省略されます。
ユーザーのサーバー
GET /users/@me/guilds には guilds スコープが必要です。
[
{ "id": "1557445672742031360", "name": "Raid Night", "icon": null,
"owner": true, "permissions": "2251799813685247", "features": [] },
{ "id": "1557445672742031361", "name": "Guild Hall", "icon": null,
"owner": false, "permissions": "1056", "features": [] }
] permissionsはサーバーを選ぶための要約で、完全な計算結果ではありません。オーナーと管理者にはすべてのビット(ADMINISTRATORを含む)、サーバー設定を編集できるユーザーにはMANAGE_GUILD(32)とVIEW_CHANNEL(1024)、それ以外のユーザーにはVIEW_CHANNELが入ります。- 1 回のレスポンスで返るサーバーは最大
limit件(1 から 200、既定値は 200)で、古い順に並びます。Discord と同様に、afterまたはbeforeに前のページのサーバーのidを指定してページングします。
現在の認可
GET /oauth2/@me はどのスコープでも使え、トークンがどのアプリのものか、何が許可されているか、いつ期限切れになるかが分かります。identify がある場合は user も含まれます。
{
"application": { "id": "123456789012345678", "name": "RaidPlanner", ... },
"scopes": ["identify", "email"],
"expires": "2026-10-15T15:03:20Z",
"user": { "id": "1557709980780474368", "username": "alice", ... }
} リンクされたロール
role_connections.write を使うと、ユーザーがあなたのサービス側でどのアカウントを使っているかを記録できます。ホストに注意してください: api.gamevox.com です。
curl -X PUT \
https://api.gamevox.com/api/users/@me/applications/123456789012345678/role-connection \
-H "Authorization: Bearer GVO.mJ4c..." \
-H "Content-Type: application/json" \
-d '{"platform_name": "RaidPlanner", "platform_username": "ShadowMage", "metadata": {}}' ユーザーには Settings > Authorized Apps に “Linked as ShadowMage on RaidPlanner” と表示されます。metadata のキーは、アプリのロール接続メタデータで定義したものでなければなりません。同じ URL に GET すると読み戻せます。
トークンの更新
アクセストークンの有効期間は 7 日です(expires_in は秒単位)。リフレッシュトークンで新しいペアを取得します。
curl -X POST https://app-api.gamevox.com/api/oauth2/token \
-u "123456789012345678:CS.your-client-secret" \
-d grant_type=refresh_token \
-d refresh_token=GVR.9tQe... - リフレッシュトークンはローテーションされます。レスポンスには新しいリフレッシュトークンが含まれ、古いリフレッシュトークンとそのアクセストークンは直ちに使えなくなります。使う前に新しいペアを保存してください。
scopeを送ると、新しいトークンのスコープを絞り込めます。ユーザーが許可していないスコープを求めるとinvalid_scopeで失敗します。- 使われていないリフレッシュトークンは 90 日で期限切れになります。1 人のユーザーが 1 つのアプリについて持てる有効なトークンペアは最大 25 組で、26 組目を作ると最も古いものが削除されます。
取り消し(ログアウト)
ユーザーがアプリからサインアウトしたら取り消してください。ペアのどちらのトークンを使っても、ペア全体が取り消されます。有効なクライアントであれば、トークンが存在したかどうかにかかわらず、エンドポイントは常に 200 を返します。
curl -X POST https://app-api.gamevox.com/api/oauth2/token/revoke \
-u "123456789012345678:CS.your-client-secret" \
-d token=GVO.mJ4c... PKCE
任意ですが、認可 URL を他人に見られうる場所で組み立てる場合は推奨します。GameVox が対応しているのは S256 だけで、トークンリクエストには引き続きクライアントシークレットが必要です。シークレットなしのパブリッククライアントはありません。
// Before redirecting: keep the verifier with the user's pending login.
const verifier = base64url(crypto.randomBytes(32));
const challenge = base64url(crypto.createHash('sha256').update(verifier).digest());
// authorize URL: ...&code_challenge=CHALLENGE&code_challenge_method=S256
// token request: ...&code_verifier=VERIFIER (client secret still required) スコープ
| スコープ | アプリができること |
|---|---|
identify | ユーザーの ID、ユーザー名、アバターを読み取ります: GET /users/@me、および GET /oauth2/@me の user。 |
email | /users/@me にユーザーの確認済みメールアドレスを追加します。 |
guilds | ユーザーのサーバー一覧を読み取ります: GET /users/@me/guilds。 |
role_connections.write | このアプリについて、ユーザーのリンクされたロールの情報を読み書きします。 |
それ以外のスコープ(connections、guilds.join、bot なしの applications.commands を含む)は、同意画面でその旨のメッセージとともに拒否されます。
エラー
トークンと取り消しのエンドポイントは、標準の OAuth2 形式 {"error": "...", "error_description": "..."} でエラーを返すため、OAuth ライブラリはそのまま読み取れます。
| error | HTTP | 意味 |
|---|---|---|
invalid_client | 401 | クライアント ID またはシークレットが誤っている、アプリが削除されている、またはアプリのオーナーのアカウントが停止されています。 |
invalid_grant | 400 | コードまたはリフレッシュトークンが不明、期限切れ、使用済み、別のアプリに発行されたもの、または redirect_uri か code_verifier が一致しません。ユーザーがアプリの認可を解除した後にも返されます。 |
invalid_request | 400 | 必須フィールドが欠けているか、本文がフォームエンコードされていません。 |
invalid_scope | 400 | リフレッシュで、元の許可に含まれないスコープが求められました。 |
unsupported_grant_type | 400 | 対応しているのは authorization_code と refresh_token だけです。 |
slow_down | 429 | あなたの IP からのクライアント認証の失敗が多すぎます(1 分あたり 30 回)。1 分待ってください。 |
不正、期限切れ、または取り消されたトークンでの API 呼び出しには 401 が返り、必要なスコープがないトークンには 403(50001 Missing Access)が返ります。レスポンスには通常の X-RateLimit-* ヘッダーが付き、ユーザーごと、アプリごとにカウントされます。
アクセスが終わるとき
トークンは期限切れの前に使えなくなることがあります。次のような場合です。
- ユーザーが Settings > Authorized Apps で Deauthorize をクリックした(そのユーザーについて保持しているトークンはすべて即座に無効になり、そのユーザーのリンクされたロールの記録も削除されます)
- ユーザーのアカウントが無効化された
- アプリケーションが削除された
- 開発者アカウントが停止された
401 や invalid_grant は “サインアウト” として扱い、ユーザーにステップ 1 からやり直してもらってください。
完全な例(Node.js + Express)
state の確認、コードの交換、ユーザー情報の読み取りを含むサインインの例です。組み込みの fetch を使うため Node 18 以降が必要です。
import express from 'express';
import crypto from 'node:crypto';
const CLIENT_ID = process.env.GAMEVOX_CLIENT_ID;
const CLIENT_SECRET = process.env.GAMEVOX_CLIENT_SECRET;
const REDIRECT_URI = 'https://yourapp.example.com/auth/gamevox';
// state must be tied to THIS browser, or someone can hand a victim a callback
// URL carrying the attacker's own code and sign them in as the attacker.
// A short-lived HttpOnly cookie does that without a session store.
const STATE_COOKIE = 'gv_oauth_state';
const readCookie = (req, name) =>
(req.headers.cookie || '').split(';').map((c) => c.trim().split('='))
.find(([k]) => k === name)?.[1] || '';
const app = express();
app.get('/login', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
res.cookie(STATE_COOKIE, state, {
httpOnly: true, secure: true, sameSite: 'lax', maxAge: 10 * 60 * 1000, path: '/auth/gamevox',
});
const url = new URL('https://gamevox.com/oauth2/authorize/');
url.search = new URLSearchParams({
response_type: 'code',
client_id: CLIENT_ID,
scope: 'identify email',
redirect_uri: REDIRECT_URI,
state,
}).toString();
res.redirect(url.toString());
});
app.get('/auth/gamevox', async (req, res) => {
const { code, state, error } = req.query;
const expected = readCookie(req, STATE_COOKIE);
res.clearCookie(STATE_COOKIE, { path: '/auth/gamevox' });
if (!expected || typeof state !== 'string' || state !== expected) {
return res.status(400).send('Login expired or started in another browser');
}
if (error) return res.redirect('/?login=cancelled');
if (typeof code !== 'string') return res.status(400).send('Missing code');
const tokenRes = await fetch('https://app-api.gamevox.com/api/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + Buffer.from(CLIENT_ID + ':' + CLIENT_SECRET).toString('base64'),
},
body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URI }),
});
if (!tokenRes.ok) return res.status(502).send('GameVox login failed');
const tokens = await tokenRes.json();
const me = await fetch('https://app-api.gamevox.com/api/users/@me', {
headers: { Authorization: 'Bearer ' + tokens.access_token },
}).then((r) => r.json());
// me.id is the stable GameVox user id: key your account record on it.
// Store tokens.refresh_token server-side if you need access later.
res.send('Signed in as ' + me.username);
});
app.listen(3000); Discord との違い
- 認可コードグラントのみです。インプリシットグラント(
response_type=token)とclient_credentialsはありません。 - パブリッククライアントはありません。PKCE の有無にかかわらず、すべてのトークンリクエストにクライアントシークレットが必要です。
- ユーザーインストール(
botなしのapplications.commands、integration_type=1)はまだ利用できません。 connections、guilds.join、guilds.members.read、rpcの各スコープはありません。/users/@me/guildsのpermissionsはサーバーを選ぶための要約で、完全な計算結果ではありません(上記参照)。