Se connecter avec GameVox
Permettez aux gens de se connecter à votre site web ou app avec leur compte GameVox. C’est le flux OAuth2 standard par code d’autorisation, calqué sur celui de Discord, et il ne nécessite aucun bot : votre app demande l’accès à certaines parties du compte, la personne accepte, et votre serveur obtient un jeton.
À quoi ça sert
- Connexion et liaison de comptes. Identifiez la personne (un ID utilisateur GameVox stable, son nom d’utilisateur, son avatar) et, avec son accord, son adresse e-mail vérifiée.
- Sélecteurs de serveur et tableaux de bord. Avec
guilds, voyez sur quels serveurs se trouve la personne et lesquels elle peut administrer. - Pas pour agir à la place de la personne. Un jeton utilisateur ne peut pas envoyer de messages, rejoindre un salon vocal ni rien modifier sur GameVox. Tout ce qui agit dans GameVox nécessite un bot.
Avant de commencer
- Créez une application sur developers.gamevox.com. L’ID client de son onglet OAuth2 est votre
client_id. - Dans l’onglet OAuth2, ajoutez chaque URI de redirection que votre app utilisera (10 au maximum). Elles doivent être en
https://, ou enhttp://localhost/http://127.0.0.1pour le développement, sans#fragment. Laredirect_urique vous envoyez doit correspondre exactement à l’une d’elles, query string comprise. - Copiez le secret client. Il n’est affiché qu’une fois, à la création de l’app ou à la réinitialisation du secret. Gardez-le sur votre serveur : chaque requête de jeton en a besoin.
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 Conservez la barre oblique finale de /oauth2/authorize/. Les endpoints de jeton et d’utilisateur répondent aussi sous /api/v10/, donc les bibliothèques qui mettent une version dans le chemin fonctionnent sans modification. Les connexions de rôle se trouvent sur api.gamevox.com, pas sur app-api.
1. Envoyer la personne vers GameVox
Redirigez le navigateur (une navigation normale, pas une popup ni une iframe) vers l’URL d’autorisation :
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 | Paramètre | Obligatoire | Description |
|---|---|---|
response_type | Oui | Toujours code. |
client_id | Oui | L’ID client de l’onglet OAuth2 de votre app. |
scope | Oui | Séparés par des espaces (encodés en URL sous la forme %20 ou +). Voir Scopes. N’incluez pas bot : avec bot, cette URL devient une installation serveur. |
redirect_uri | Oui | L’une de vos URI de redirection enregistrées, à l’identique. |
state | Non | Une valeur impossible à deviner, liée à la session de la personne (1024 caractères au maximum). Elle revient inchangée ; vérifiez-la pour bloquer les attaques par falsification de requête intersite (CSRF). |
code_challenge | Non | Challenge PKCE facultatif, de 43 à 128 caractères. |
code_challenge_method | Non | S256 quand vous envoyez un challenge. plain est refusé. |
prompt | Non | none saute l’écran de consentement quand la personne a déjà autorisé votre app pour tous ces scopes. Tout nouveau scope déclenche quand même la demande. |
Ce que voit la personne
- Un écran de consentement sur gamevox.com avec le nom, l’icône et la description de votre app, le compte avec lequel elle est connectée, la liste en langage clair de ce que vous pourrez voir, et le site vers lequel elle sera renvoyée.
- Si elle n’est pas connectée, le bouton « Log in » la fait passer par l’app web GameVox puis la ramène directement sur ce même écran.
- Les liens vers vos conditions d’utilisation et votre politique de confidentialité s’affichent sous les boutons si vous les avez renseignés dans l’onglet Informations générales.
2. Traiter le retour
Une fois que la personne a cliqué sur « Authorize », GameVox la renvoie vers votre URI de redirection avec un code :
https://yourapp.example.com/auth/gamevox?code=3vR0b...&state=Zx81kq0vT2 Si elle clique plutôt sur « Cancel » :
https://yourapp.example.com/auth/gamevox?error=access_denied
&error_description=The+user+denied+the+request&state=Zx81kq0vT2 - Rejetez le retour si
staten’est pas celui que vous avez émis pour ce navigateur. - Un code expire au bout de 10 minutes et ne sert qu’une seule fois. Échangez-le immédiatement, depuis votre serveur.
3. Échanger le code contre des jetons
Depuis votre serveur, envoyez en POST un corps encodé en formulaire (application/x-www-form-urlencoded, pas du JSON) à l’endpoint de jeton :
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"
} - Authentifiez-vous en HTTP Basic (
client_id:client_secret) comme ci-dessus, ou envoyezclient_idetclient_secretcomme champs de formulaire. redirect_uridoit être exactement la valeur envoyée à l’étape 1. Ajoutezcode_verifiersi vous avez utilisé PKCE.- Les réponses portent
Cache-Control: no-store. Ne mettez jamais le secret client ni les jetons dans du code côté navigateur ou dans des URL.
4. Appeler l’API
Envoyez le jeton d’accès en Authorization: Bearer. GET /users/@me nécessite le 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
} idest l’ID GameVox stable de la personne. Indexez vos comptes dessus ; les noms d’utilisateur peuvent changer.gamevox_iddésigne la même personne sous la forme UUID native de GameVox.emailetverifiedne sont présents qu’avec le scopeemail, et seulement si la personne a une adresse e-mail enregistrée. GameVox ne conserve que des adresses vérifiées, doncverifiedvaut toujourstruequandemailest renseigné.avatarvaut toujoursnull(les bibliothèques Discord construiraient à partir de lui un lien vers le CDN de Discord). Utilisezgamevox_avatar_url, qui est omis quand la personne n’a pas de photo.
Les serveurs de la personne
GET /users/@me/guilds nécessite le 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": [] }
] permissionsest un résumé destiné à choisir un serveur, pas un calcul complet : tous les bits (y comprisADMINISTRATOR) pour le propriétaire et les administrateurs,MANAGE_GUILD(32) plusVIEW_CHANNEL(1024) là où la personne peut modifier les réglages du serveur, etVIEW_CHANNELpour tous les autres.- Jusqu’à
limitserveurs par réponse (de 1 à 200, 200 par défaut), du plus ancien au plus récent. Paginez avecafteroubeforedéfini sur l’idd’un serveur de la page précédente, comme sur Discord.
L’autorisation en cours
GET /oauth2/@me fonctionne avec n’importe quel scope et indique à quelle app appartient le jeton, ce qui lui a été accordé et quand il expire. user est inclus avec identify.
{
"application": { "id": "123456789012345678", "name": "RaidPlanner", ... },
"scopes": ["identify", "email"],
"expires": "2026-10-15T15:03:20Z",
"user": { "id": "1557709980780474368", "username": "alice", ... }
} Rôles liés
Avec role_connections.write, vous pouvez enregistrer le compte que la personne possède de votre côté. Attention à l’hôte : 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 personne le voit dans Settings > Authorized Apps sous la forme « Linked as ShadowMage on RaidPlanner ». Les clés de metadata doivent être celles que vous avez définies dans les métadonnées de connexion de rôle de votre app. Un GET sur la même URL les relit.
Rafraîchissement
Les jetons d’accès durent 7 jours (expires_in est en secondes). Obtenez une nouvelle paire avec le jeton de rafraîchissement :
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... - Les jetons de rafraîchissement tournent. La réponse contient un nouveau jeton de rafraîchissement, et l’ancien jeton de rafraîchissement ainsi que son jeton d’accès cessent immédiatement de fonctionner : enregistrez la nouvelle paire avant de l’utiliser.
- Vous pouvez envoyer
scopepour limiter le nouveau jeton à moins de scopes. Demander un scope que la personne n’a jamais accordé échoue avecinvalid_scope. - Un jeton de rafraîchissement inutilisé expire au bout de 90 jours. Chaque personne conserve au plus 25 paires de jetons actives par app ; en créer une 26e supprime la plus ancienne.
Révocation (déconnexion)
Révoquez quand la personne se déconnecte de votre app. N’importe quel jeton d’une paire révoque toute la paire. L’endpoint répond toujours 200 pour un client valide, que le jeton ait existé ou non.
curl -X POST https://app-api.gamevox.com/api/oauth2/token/revoke \
-u "123456789012345678:CS.your-client-secret" \
-d token=GVO.mJ4c... PKCE
Facultatif, et recommandé si l’URL d’autorisation peut être construite à un endroit où quelqu’un d’autre pourrait la voir. GameVox ne prend en charge que S256, et une requête de jeton exige quand même votre secret client : il n’existe pas de clients publics sans secret.
// 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 | Permet à votre app de |
|---|---|
identify | Lire l’ID, le nom d’utilisateur et l’avatar de la personne : GET /users/@me, et user dans GET /oauth2/@me. |
email | Ajouter l’adresse e-mail vérifiée de la personne à /users/@me. |
guilds | Lire la liste des serveurs de la personne : GET /users/@me/guilds. |
role_connections.write | Lire et écrire les informations de rôle lié de la personne pour votre app. |
Tout autre scope, y compris connections, guilds.join et applications.commands sans bot, est refusé sur l’écran de consentement avec un message qui l’indique.
Erreurs
Les endpoints de jeton et de révocation renvoient les erreurs au format OAuth2 standard, {"error": "...", "error_description": "..."}, que les bibliothèques OAuth lisent telles quelles :
| error | HTTP | Signification |
|---|---|---|
invalid_client | 401 | ID client ou secret incorrect, app supprimée, ou compte du propriétaire de l’app suspendu. |
invalid_grant | 400 | Le code ou le jeton de rafraîchissement est inconnu, expiré, déjà utilisé, émis pour une autre app, ou son redirect_uri ou son code_verifier ne correspond pas. Également renvoyé après que la personne a retiré l’autorisation de votre app. |
invalid_request | 400 | Un champ obligatoire manque, ou le corps n’est pas encodé en formulaire. |
invalid_scope | 400 | Un rafraîchissement a demandé un scope que l’octroi initial n’inclut pas. |
unsupported_grant_type | 400 | Seuls authorization_code et refresh_token sont pris en charge. |
slow_down | 429 | Trop d’échecs d’authentification client depuis votre IP (30 par minute). Patientez une minute. |
Les appels d’API avec un jeton invalide, expiré ou révoqué reçoivent 401 ; un jeton sans le scope nécessaire reçoit 403 (50001 Missing Access). Les réponses portent les en-têtes X-RateLimit-* habituels, comptés par personne et par app.
Quand l’accès prend fin
Un jeton peut cesser de fonctionner avant son expiration. Cela arrive quand :
- la personne clique sur Deauthorize dans Settings > Authorized Apps (tous les jetons que vous détenez pour elle sont invalidés d’un coup, et votre entrée de rôle lié la concernant est supprimée)
- le compte de la personne est désactivé
- votre application est supprimée
- votre compte développeur est suspendu
Traitez un 401 ou un invalid_grant comme « déconnecté » et refaites passer la personne par l’étape 1.
Exemple complet (Node.js + Express)
Connexion avec vérification du state, échange du code et lecture des informations de la personne. Node 18+ pour le fetch intégré.
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); Différences avec Discord
- Uniquement l’octroi par code d’autorisation. Pas d’octroi implicite (
response_type=token) ni declient_credentials. - Pas de clients publics : chaque requête de jeton exige le secret client, avec ou sans PKCE.
- Les installations utilisateur (
applications.commandssansbot,integration_type=1) ne sont pas encore disponibles. - Pas de scopes
connections,guilds.join,guilds.members.readnirpc. - Dans
/users/@me/guilds,permissionsest un résumé destiné à choisir un serveur, pas un calcul complet (voir plus haut).