Iniciar sesión con GameVox
Permite que las personas inicien sesión en tu sitio web o app con su cuenta de GameVox. Es el flujo estándar OAuth2 de código de autorización, con la misma forma que el de Discord, y no necesita bot: tu app pide acceso a partes de la cuenta de la persona, esta lo aprueba y tu servidor recibe un token.
Para qué sirve
- Inicio de sesión y vinculación de cuentas. Averigua quién es la persona (un ID de usuario de GameVox estable, nombre de usuario, avatar) y, con su permiso, su correo verificado.
- Selectores de servidor y dashboards. Con
guilds, ves en qué servidores está la persona y en cuáles puede gestionar los ajustes. - No sirve para actuar en nombre de la persona. Un token de usuario no puede enviar mensajes, unirse a voz ni cambiar nada en GameVox. Todo lo que actúe dentro de GameVox necesita un bot.
Antes de empezar
- Crea una aplicación en developers.gamevox.com. El ID de cliente de su pestaña OAuth2 es tu
client_id. - En la pestaña OAuth2, añade cada URI de redirección que vaya a usar tu app (hasta 10). Deben ser
https://, ohttp://localhost/http://127.0.0.1para desarrollo, sin#fragment. Laredirect_urique envíes debe coincidir exactamente con una de ellas, incluida la cadena de consulta. - Copia el secreto de cliente. Se muestra una sola vez, al crear la app o al restablecer el secreto. Guárdalo en tu servidor: toda solicitud de token lo necesita.
Endpoints
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 Mantén la barra final en /oauth2/authorize/. Los endpoints de token y de usuario también responden bajo /api/v10/, así que las bibliotecas que ponen una versión en la ruta funcionan sin cambios. Las conexiones de rol están en api.gamevox.com, no en app-api.
1. Envía a la persona a GameVox
Redirige el navegador (con una navegación normal, no en una ventana emergente ni en un iframe) a la URL de autorización:
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 | Parámetro | Obligatorio | Descripción |
|---|---|---|
response_type | Sí | Siempre code. |
client_id | Sí | El ID de cliente de la pestaña OAuth2 de tu app. |
scope | Sí | Separados por espacios (codificados en URL como %20 o +). Consulta Scopes. No incluyas bot: con bot, esta URL pasa a ser una instalación en servidor. |
redirect_uri | Sí | Una de tus URIs de redirección registradas, exactamente igual. |
state | No | Un valor imposible de adivinar, ligado a la sesión de la persona (hasta 1024 caracteres). Vuelve sin cambios; compruébalo para evitar la falsificación de solicitudes entre sitios. |
code_challenge | No | Reto PKCE opcional, de 43 a 128 caracteres. |
code_challenge_method | No | S256 cuando envíes un reto. plain se rechaza. |
prompt | No | none omite la pantalla de consentimiento si la persona ya aprobó tu app para todos estos scopes. Cualquier cosa nueva se sigue preguntando. |
Lo que ve la persona
- Una pantalla de consentimiento en gamevox.com con el nombre, el icono y la descripción de tu app, la cuenta con la que ha iniciado sesión, una lista en lenguaje claro de lo que podrás ver y el sitio al que se la enviará.
- Si no ha iniciado sesión, «Log in» la lleva por la app web de GameVox y la devuelve directamente a la misma pantalla.
- Los enlaces a tus Términos del servicio y tu Política de privacidad aparecen bajo los botones si los defines en la pestaña Información general.
2. Gestiona el callback
Cuando la persona pulsa «Authorize», GameVox la envía a tu URI de redirección con un código:
https://yourapp.example.com/auth/gamevox?code=3vR0b...&state=Zx81kq0vT2 Si en cambio pulsa «Cancel»:
https://yourapp.example.com/auth/gamevox?error=access_denied
&error_description=The+user+denied+the+request&state=Zx81kq0vT2 - Rechaza el callback si
stateno es el que emitiste para este navegador. - Un código caduca a los 10 minutos y funciona una sola vez. Canjéalo de inmediato, desde tu servidor.
3. Canjea el código por tokens
Desde tu servidor, haz un POST con un cuerpo codificado como formulario (application/x-www-form-urlencoded, no JSON) al endpoint de token:
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"
} - Autentícate con HTTP Basic (
client_id:client_secret) como arriba, o envíaclient_idyclient_secretcomo campos del formulario. redirect_uridebe ser el mismo valor que enviaste en el paso 1. Añadecode_verifiersi usaste PKCE.- Las respuestas llevan
Cache-Control: no-store. Nunca pongas el secreto de cliente ni los tokens en código del navegador ni en URLs.
4. Llama a la API
Envía el token de acceso como Authorization: Bearer. GET /users/@me necesita el scope 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
} ides el ID estable de GameVox de la persona. Úsalo como clave de tus registros de cuentas; los nombres de usuario pueden cambiar.gamevox_ides la misma persona en el formato UUID nativo de GameVox.emailyverifiedsolo aparecen con el scopeemail, y solo si la persona tiene un correo registrado. GameVox solo guarda direcciones verificadas, así queverifiedsiempre estruecuandoemailestá presente.avatarsiempre esnull(las bibliotecas de Discord construirían con él un enlace a la CDN de Discord). Usagamevox_avatar_url, que se omite cuando la persona no tiene imagen.
Los servidores de la persona
GET /users/@me/guilds necesita el scope 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": [] }
] permissionses un resumen para elegir servidores, no un cálculo completo: todos los bits (incluidoADMINISTRATOR) para la persona propietaria y los admins,MANAGE_GUILD(32) másVIEW_CHANNEL(1024) donde la persona puede editar los ajustes del servidor, yVIEW_CHANNELpara el resto.- Hasta
limitservidores por respuesta (de 1 a 200, 200 por defecto), del más antiguo al más reciente. Pagina conafterobeforefijado en elidde un servidor de la página anterior, como en Discord.
La autorización actual
GET /oauth2/@me funciona con cualquier scope y te dice a qué app pertenece el token, qué se le concedió y cuándo caduca. user se incluye con identify.
{
"application": { "id": "123456789012345678", "name": "RaidPlanner", ... },
"scopes": ["identify", "email"],
"expires": "2026-10-15T15:03:20Z",
"user": { "id": "1557709980780474368", "username": "alice", ... }
} Roles vinculados
Con role_connections.write puedes registrar qué cuenta tiene la persona en tu servicio. Fíjate en el host: 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": {}}' La persona lo ve en Settings > Authorized Apps como “Linked as ShadowMage on RaidPlanner”. Las claves de metadata deben ser las que definiste en los metadatos de conexión de rol de tu app. Un GET a la misma URL lo devuelve.
Renovación
Los tokens de acceso duran 7 días (expires_in está en segundos). Obtén un par nuevo con el token de actualización:
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... - Los tokens de actualización rotan. La respuesta trae un token de actualización nuevo, y el anterior y su token de acceso dejan de funcionar de inmediato, así que guarda el par nuevo antes de usarlo.
- Puedes enviar
scopepara limitar el token nuevo a menos scopes. Pedir un scope que la persona nunca concedió falla coninvalid_scope. - Un token de actualización sin usar caduca a los 90 días. Cada persona conserva como máximo 25 pares de tokens activos por app; crear el 26.º elimina el más antiguo.
Revocación (cerrar sesión)
Revoca cuando la persona cierre sesión en tu app. Cualquiera de los dos tokens de un par revoca el par completo. El endpoint siempre responde 200 a un cliente válido, exista o no el token.
curl -X POST https://app-api.gamevox.com/api/oauth2/token/revoke \
-u "123456789012345678:CS.your-client-secret" \
-d token=GVO.mJ4c... PKCE
Opcional, y recomendado si la URL de autorización puede construirse donde otra persona podría verla. GameVox solo admite S256, y una solicitud de token sigue necesitando tu secreto de cliente: no hay clientes públicos sin secreto.
// 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) Scopes
| Scope | Permite a tu app |
|---|---|
identify | Leer el ID, el nombre de usuario y el avatar de la persona: GET /users/@me, y user en GET /oauth2/@me. |
email | Añadir el correo verificado de la persona a /users/@me. |
guilds | Leer la lista de servidores de la persona: GET /users/@me/guilds. |
role_connections.write | Leer y escribir los datos de rol vinculado de la persona para tu app. |
Cualquier otro scope, incluidos connections, guilds.join y applications.commands sin bot, se rechaza en la pantalla de consentimiento con un mensaje que lo indica.
Errores
Los endpoints de token y de revocación devuelven los errores con la forma estándar de OAuth2, {"error": "...", "error_description": "..."}, así que las bibliotecas OAuth los leen tal cual:
| error | HTTP | Significado |
|---|---|---|
invalid_client | 401 | ID de cliente o secreto incorrectos, app eliminada, o la cuenta propietaria de la app está suspendida. |
invalid_grant | 400 | El código o el token de actualización es desconocido, ha caducado, ya se usó o se emitió para otra app, o su redirect_uri o code_verifier no coincide. También se devuelve después de que la persona desautorice tu app. |
invalid_request | 400 | Falta un campo obligatorio, o el cuerpo no está codificado como formulario. |
invalid_scope | 400 | Una renovación pidió un scope que la concesión original no incluye. |
unsupported_grant_type | 400 | Solo se admiten authorization_code y refresh_token. |
slow_down | 429 | Demasiadas autenticaciones de cliente fallidas desde tu IP (30 por minuto). Espera un minuto. |
Las llamadas a la API con un token incorrecto, caducado o revocado reciben 401; un token sin el scope necesario recibe 403 (50001 Missing Access). Las respuestas llevan las cabeceras X-RateLimit-* habituales, contadas por persona y por app.
Cuándo termina el acceso
Un token puede dejar de funcionar antes de caducar. Ocurre cuando:
- la persona pulsa Deauthorize en Settings > Authorized Apps (todos los tokens que tengas para ella dejan de funcionar al instante, y se elimina tu entrada de rol vinculado para ella)
- la cuenta de la persona se desactiva
- tu aplicación se elimina
- tu cuenta de desarrollador se suspende
Trata un 401 o un invalid_grant como “sesión cerrada” y vuelve a llevar a la persona por el paso 1.
Ejemplo completo (Node.js + Express)
Inicio de sesión con comprobación de state, canje del código y lectura de los datos de la persona. Node 18+ por el fetch integrado.
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); Diferencias con Discord
- Solo la concesión de código de autorización. Sin concesión implícita (
response_type=token) niclient_credentials. - Sin clientes públicos: toda solicitud de token necesita el secreto de cliente, con o sin PKCE.
- Las instalaciones de usuario (
applications.commandssinbot,integration_type=1) todavía no están disponibles. - Sin los scopes
connections,guilds.join,guilds.members.readnirpc. - En
/users/@me/guilds,permissionses un resumen para elegir servidores, no un cálculo completo (ver arriba).