Вход через GameVox
Позвольте людям входить на ваш сайт или в приложение со своим аккаунтом GameVox. Это стандартный процесс OAuth2 с кодом авторизации (authorization code), устроенный так же, как в Discord, и бот для него не нужен: ваше приложение запрашивает доступ к части данных аккаунта, пользователь соглашается, и ваш сервер получает токен.
Для чего это нужно
- Вход и привязка аккаунтов. Узнайте, кто пользователь (постоянный ID пользователя GameVox, имя пользователя, аватар), а с его разрешения и его подтверждённый адрес электронной почты.
- Выбор сервера и дашборды. Со скоупом
guildsвидно, на каких серверах состоит пользователь и где он может управлять настройками. - Не для действий от имени пользователя. Пользовательский токен не может отправлять сообщения, подключаться к голосовым каналам или что-либо менять в GameVox. Для любых действий внутри GameVox нужен бот.
Перед началом
- Создайте приложение на developers.gamevox.com. ID клиента на его вкладке OAuth2 и есть ваш
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/, поэтому библиотеки, которые добавляют версию в путь, работают без изменений. Связи ролей (role connections) находятся на api.gamevox.com, а не на app-api.
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 | Да | ID клиента с вкладки OAuth2 вашего приложения. |
scope | Да | Через пробел (в URL-кодировке %20 или +). См. Скоупы. Не указывайте bot: с bot этот URL превращается в установку на сервер. |
redirect_uri | Да | Один из ваших зарегистрированных URI перенаправления, символ в символ. |
state | Нет | Неугадываемое значение, привязанное к сессии пользователя (до 1024 символов). Оно возвращается без изменений; проверяйте его для защиты от подделки межсайтовых запросов (CSRF). |
code_challenge | Нет | Необязательный challenge для PKCE, от 43 до 128 символов. |
code_challenge_method | Нет | S256, если вы передаёте challenge. plain отклоняется. |
prompt | Нет | none пропускает экран согласия, если пользователь уже одобрил ваше приложение для всех этих скоупов. Если запрошено что-то новое, экран всё равно появится. |
Что видит пользователь
- Экран согласия на gamevox.com: название, значок и описание вашего приложения, аккаунт, под которым пользователь вошёл, понятный список того, что вы сможете видеть, и сайт, на который его отправят.
- Если пользователь не вошёл в аккаунт, кнопка «Log in» проведёт его через веб-приложение GameVox и сразу вернёт на тот же экран.
- Ссылки на ваши Условия использования и Политику конфиденциальности появляются под кнопками, если вы указали их на вкладке Общая информация.
2. Обработайте возврат (callback)
Когда пользователь нажмёт «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 - Отклоняйте callback, если
stateне совпадает с тем, что вы выдали этому браузеру. - Код действует 10 минут и срабатывает только один раз. Обменяйте его сразу, со своего сервера.
3. Обменяйте код на токены
Со своего сервера отправьте POST с телом в формате формы (application/x-www-form-urlencoded, не JSON) на эндпоинт токенов:
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хранится постоянный ID пользователя GameVox. Привязывайте к нему записи аккаунтов: имена пользователей могут меняться.gamevox_idобозначает того же пользователя в собственном формате GameVox (UUID). emailиverifiedприсутствуют только со скоупомemailи только если у пользователя указан адрес электронной почты. GameVox хранит только подтверждённые адреса, поэтомуverifiedвсегда равноtrue, когда заданemail.avatarвсегда равноnull(библиотеки Discord построили бы из него ссылку на CDN Discord). Используйте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для всех остальных. - До
limitсерверов в одном ответе (от 1 до 200, по умолчанию 200), сначала самые старые. Для постраничной загрузки передайте вafterилиbeforeidсервера из предыдущей страницы, как в Discord.
Текущая авторизация
GET /oauth2/@me работает с любым скоупом и сообщает, какому приложению принадлежит токен, что ему выдано и когда он истекает. user включается при наличии identify.
{
"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 должны быть из тех, что вы определили в метаданных связей ролей своего приложения. GET по тому же URL возвращает сохранённые данные.
Обновление токенов
Токены доступа действуют 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 дней. У каждого пользователя может быть не более 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, а также user в GET /oauth2/@me. |
email | Добавляет подтверждённый адрес электронной почты пользователя в /users/@me. |
guilds | Чтение списка серверов пользователя: GET /users/@me/guilds. |
role_connections.write | Чтение и запись данных связанной роли пользователя для вашего приложения. |
Любой другой скоуп, включая connections, guilds.join и applications.commands без bot, отклоняется на экране согласия с соответствующим сообщением.
Ошибки
Эндпоинты токенов и отзыва возвращают ошибки в стандартном для 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 (30 в минуту). Подождите минуту. |
Вызовы API с неверным, истёкшим или отозванным токеном получают 401; токен без нужного скоупа получает 403 (50001 Missing Access). Ответы содержат обычные заголовки X-RateLimit-*, лимиты считаются отдельно для каждого пользователя в каждом приложении.
Когда доступ прекращается
Токен может перестать работать раньше срока. Это происходит, если:
- пользователь нажал Deauthorize в Settings > Authorized Apps (все ваши токены для этого пользователя сразу перестают действовать, а ваша запись связанной роли для него удаляется)
- аккаунт пользователя отключён
- ваше приложение удалено
- ваш аккаунт разработчика заблокирован
Считайте 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
- Только грант authorization code. Нет implicit-гранта (
response_type=token) и нетclient_credentials. - Нет публичных клиентов: каждому запросу токена нужен секрет клиента, с PKCE или без.
- Установки пользователю (
applications.commandsбезbot,integration_type=1) пока недоступны. - Нет скоупов
connections,guilds.join,guilds.members.readиrpc. - В
/users/@me/guildsполеpermissionsслужит сводкой для выбора сервера, а не полным расчётом (см. выше).