OAuth2 — "Se connecter avec BloumeChat"
Ce guide construit un flux "Login with BloumeChat" complet pour une application tierce (site web, service backend) : redirection vers l'écran d'autorisation, échange du code contre un access token, puis récupération du profil de l'utilisateur. C'est un flux web/HTTP, distinct du SDK bot (client.login(BOT_TOKEN)) documenté dans le Démarrage rapide — vous n'avez pas besoin du package bloumechat pour l'implémenter, uniquement des requêtes HTTP.
Ce dont vous avez besoin
Une application créée sur le Portail Développeurs (menu Mon compte → Développeurs). Depuis l'onglet OAuth2 de votre application, récupérez :
- le Client ID
- le Client Secret (gardez-le secret — ne l'exposez jamais côté navigateur)
- au moins une URI de redirection enregistrée (doit correspondre exactement à celle utilisée dans le flux, protocole compris)
Vue d'ensemble du flux
BloumeChat implémente le grant Authorization Code (RFC 6749) en 3 étapes :
- Vous redirigez l'utilisateur vers
/oauth2/authorizeavec votreclient_idet lesscopedemandés. - L'utilisateur accepte sur l'écran d'autorisation BloumeChat → il est redirigé vers votre
redirect_uriavec uncodeen paramètre de requête. - Votre serveur échange ce
codecontre unaccess_tokenen appelantPOST /api/v2/oauth2/tokenavec votreclient_secret— cet échange doit se faire côté serveur, jamais côté client, puisqu'il nécessite le secret.
Utilisateur Votre app (serveur) BloumeChat
| | |
|------ 1. clique "Login" ---->| |
|<----- redirige vers /oauth2/authorize?client_id=...&scope=...&redirect_uri=... --|
|------------------------- 2. accepte -------------------------------------------->|
|<----------------------- redirige vers redirect_uri?code=XXXX ---------------------|
| |--- 3. POST /oauth2/token (code + client_secret) -->|
| |<-------------------- access_token -----------------|
| |--- 4. GET /oauth2/resources (access_token) ------->|
| |<-------------------- profil utilisateur -----------|Étape 1 — Rediriger vers l'écran d'autorisation
Construisez l'URL suivante et redirigez-y l'utilisateur (bouton "Se connecter avec BloumeChat" ou redirection automatique) :
https://bloumechat.com/oauth2/authorize
?client_id=VOTRE_CLIENT_ID
&redirect_uri=https%3A%2F%2Fvotresite.com%2Fcallback
&scope=identify%20emailfunction buildAuthorizeUrl(clientId: string, redirectUri: string, scopes: string[]) {
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope: scopes.join(" "),
});
return `https://bloumechat.com/oauth2/authorize?${params.toString()}`;
}
const url = buildAuthorizeUrl(
process.env.BLOUMECHAT_CLIENT_ID!,
"https://votresite.com/callback",
["identify", "email"]
);function buildAuthorizeUrl(clientId, redirectUri, scopes) {
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope: scopes.join(" "),
});
return `https://bloumechat.com/oauth2/authorize?${params.toString()}`;
}
const url = buildAuthorizeUrl(
process.env.BLOUMECHAT_CLIENT_ID,
"https://votresite.com/callback",
["identify", "email"]
);Si l'utilisateur n'est pas connecté à BloumeChat, il est d'abord redirigé vers l'écran de login puis renvoyé automatiquement vers l'écran d'autorisation. S'il a déjà autorisé votre application avec exactement les mêmes scopes, l'écran d'autorisation est sauté et il est redirigé immédiatement.
Pas de paramètre state
Le flux actuel ne transmet aucun paramètre state (anti-CSRF standard OAuth2) à travers la redirection — redirect_uri ne recevra que ?code=.... Si vous avez besoin de lier la redirection à une session initiale côté serveur (empêcher un CSRF sur le callback), gérez-le vous-même : générez un identifiant de session avant la redirection, stockez-le côté serveur (cookie signé, session store), et validez le contexte à la réception du callback plutôt que de compter sur un state renvoyé par BloumeChat.
Scopes disponibles
| Scope | Description | Effet |
|---|---|---|
identify | Profil de base (nom, tag, ID public, avatar) | Toujours inclus par défaut si aucun scope n'est précisé |
email | Adresse e-mail du compte | Ajoute email à la réponse de /oauth2/resources |
guilds | Indique que l'utilisateur consent à partager la liste de ses serveurs | Accepté par l'écran d'autorisation ; aucun endpoint public ne l'expose actuellement — n'implémentez pas de logique côté client qui en dépend |
guilds.join | Ajouter l'utilisateur à un serveur précis lors de l'autorisation | Nécessite server_id et que le bot de l'application soit déjà membre de ce serveur avec la permission CREATE_INVITE (ou ADMINISTRATOR) — la permission requise porte sur le bot, pas sur l'utilisateur qui autorise. Voir Permissions |
bot | Ajouter le bot de l'application à un serveur | Nécessite server_id et que l'utilisateur qui autorise ait la permission MANAGE_APPLICATIONS (ou soit propriétaire/admin) sur ce serveur — installer un bot est un acte privilégié, donc la permission porte ici sur cet utilisateur ; crée le compte bot à la volée s'il n'existe pas encore |
Plusieurs scopes se combinent avec un espace : scope=identify email.
guilds.join et bot nécessitent un server_id
Ces deux scopes ne fonctionnent que si vous passez également server_id=PUBLIC_ID_DU_SERVEUR dans l'URL d'autorisation (ou laissez l'utilisateur choisir un serveur sur l'écran d'autorisation lui-même, comme le fait le portail développeurs). Sans serveur cible, ces scopes sont ignorés silencieusement — et guilds.join est également ignoré silencieusement (sans erreur renvoyée, le login/liaison de compte réussit normalement) si le bot de l'application n'est pas encore membre du serveur ciblé ou n'y a pas la permission CREATE_INVITE : seul l'ajout au serveur n'a alors pas lieu.
Étape 2 — Récupérer le code sur votre callback
Votre redirect_uri reçoit ?code=XXXX en paramètre de requête. Le code expire après 10 minutes et n'est utilisable qu'une seule fois.
app.get("/callback", async (req, res) => {
const code = req.query.code as string | undefined;
if (!code) return res.status(400).send("Code manquant");
// Étape 3 : échanger le code contre un token (voir ci-dessous)
const tokens = await exchangeCode(code);
// ... créer votre propre session applicative, stocker tokens.access_token, etc.
res.redirect("/dashboard");
});Étape 3 — Échanger le code contre un access token
Depuis votre serveur (jamais depuis le navigateur, le client_secret ne doit jamais être exposé côté client), appelez :
POST https://bloumechat.com/api/v2/oauth2/token
async function exchangeCode(code: string) {
const res = await fetch("https://bloumechat.com/api/v2/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code,
client_id: process.env.BLOUMECHAT_CLIENT_ID,
client_secret: process.env.BLOUMECHAT_CLIENT_SECRET,
redirect_uri: "https://votresite.com/callback",
}),
});
if (!res.ok) throw new Error(`Échange du code échoué (${res.status})`);
return res.json() as Promise<{
access_token: string;
token_type: "Bearer";
expires_in: number; // toujours 3600 (1h)
refresh_token: string;
scope: string;
}>;
}curl -X POST https://bloumechat.com/api/v2/oauth2/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "LE_CODE_RECU",
"client_id": "VOTRE_CLIENT_ID",
"client_secret": "VOTRE_CLIENT_SECRET",
"redirect_uri": "https://votresite.com/callback"
}'Réponse (200 OK) :
{
"access_token": "a1b2c3d4...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "e5f6g7h8...",
"scope": "identify email"
}redirect_uri doit être strictement identique (caractère pour caractère) à celui utilisé à l'étape 1, sinon l'échange échoue avec invalid_redirect_uri. Le endpoint accepte aussi bien application/json que application/x-www-form-urlencoded (compatibilité avec les clients OAuth2 standards qui postent des form data).
refresh_token n'est pas actuellement échangeable
Un refresh_token est bien renvoyé dans la réponse, mais le endpoint /oauth2/token ne traite que grant_type=authorization_code — il n'existe pas de route acceptant grant_type=refresh_token pour l'instant. L'access token expire après 1h et ne peut pas être rafraîchi : votre application doit détecter l'expiration (via expires_in ou une réponse d'échec de /oauth2/resources) et faire refaire le flux complet à l'utilisateur (retour à l'étape 1) pour obtenir un nouveau token. Ne construisez pas de logique de refresh silencieux basée sur refresh_token — elle échouera.
Étape 4 — Récupérer le profil de l'utilisateur
GET https://bloumechat.com/api/v2/oauth2/resources
Endpoint hérité — paramètres de requête, pas de header Authorization
Contrairement à la convention Bearer standard (Authorization: Bearer <token>), cet endpoint attend l'access token et le secret de votre application en paramètres de requête : access_tokens et app_secret. C'est un format historique conservé pour compatibilité — vérifiez ce comportement si vous générez un client OAuth2 générique, il ne suivra pas cette convention par défaut.
async function fetchProfile(accessToken: string) {
const url = new URL("https://bloumechat.com/api/v2/oauth2/resources");
url.searchParams.set("access_tokens", accessToken);
url.searchParams.set("app_secret", process.env.BLOUMECHAT_CLIENT_SECRET!);
const res = await fetch(url.toString());
const data = await res.json();
if (!data.success) throw new Error("Token invalide ou expiré");
return data.info_user as {
id: string; // publicId de l'utilisateur
username: string;
avatar: string;
email: string | null; // null si le scope "email" n'a pas été demandé
emailVerified: boolean;
};
}Réponse en cas de succès :
{
"success": true,
"info_user": {
"id": "1234567890123456",
"username": "julesbeaufort",
"avatar": "https://cdn.bloumechat.com/avatars/....png",
"email": "[email protected]",
"emailVerified": true
},
"access_tokens": "a1b2c3d4..."
}email est null si le token n'a pas été émis avec le scope email, même si le compte a une adresse enregistrée. id est le publicId (Snowflake) de l'utilisateur — jamais un identifiant interne de base de données, utilisez-le comme identifiant stable pour lier le compte BloumeChat à un compte dans votre propre système.
Exemple complet (Express)
import express from "express";
const app = express();
const CLIENT_ID = process.env.BLOUMECHAT_CLIENT_ID!;
const CLIENT_SECRET = process.env.BLOUMECHAT_CLIENT_SECRET!;
const REDIRECT_URI = "https://votresite.com/callback";
app.get("/login", (req, res) => {
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: "identify email",
});
res.redirect(`https://bloumechat.com/oauth2/authorize?${params.toString()}`);
});
app.get("/callback", async (req, res) => {
const code = req.query.code as string | undefined;
if (!code) return res.status(400).send("Autorisation refusée ou code manquant");
const tokenRes = await fetch("https://bloumechat.com/api/v2/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
}),
});
if (!tokenRes.ok) return res.status(502).send("Échange du token échoué");
const { access_token } = await tokenRes.json();
const profileUrl = new URL("https://bloumechat.com/api/v2/oauth2/resources");
profileUrl.searchParams.set("access_tokens", access_token);
profileUrl.searchParams.set("app_secret", CLIENT_SECRET);
const profileRes = await fetch(profileUrl.toString());
const { success, info_user } = await profileRes.json();
if (!success) return res.status(502).send("Profil introuvable");
// À vous de créer votre propre session applicative ici (cookie signé, JWT, etc.)
// en utilisant info_user.id (publicId) comme identifiant stable.
req.session.userId = info_user.id;
req.session.username = info_user.username;
res.redirect("/dashboard");
});
app.listen(3000);const express = require("express");
const app = express();
const CLIENT_ID = process.env.BLOUMECHAT_CLIENT_ID;
const CLIENT_SECRET = process.env.BLOUMECHAT_CLIENT_SECRET;
const REDIRECT_URI = "https://votresite.com/callback";
app.get("/login", (req, res) => {
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: "identify email",
});
res.redirect(`https://bloumechat.com/oauth2/authorize?${params.toString()}`);
});
app.get("/callback", async (req, res) => {
const code = req.query.code;
if (!code) return res.status(400).send("Autorisation refusée ou code manquant");
const tokenRes = await fetch("https://bloumechat.com/api/v2/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "authorization_code",
code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
}),
});
if (!tokenRes.ok) return res.status(502).send("Échange du token échoué");
const { access_token } = await tokenRes.json();
const profileUrl = new URL("https://bloumechat.com/api/v2/oauth2/resources");
profileUrl.searchParams.set("access_tokens", access_token);
profileUrl.searchParams.set("app_secret", CLIENT_SECRET);
const profileRes = await fetch(profileUrl.toString());
const { success, info_user } = await profileRes.json();
if (!success) return res.status(502).send("Profil introuvable");
req.session.userId = info_user.id;
req.session.username = info_user.username;
res.redirect("/dashboard");
});
app.listen(3000);Gestion des erreurs
POST /oauth2/token et GET /oauth2/resources — les deux endpoints appelés directement par votre serveur — renvoient un format d'erreur standard OAuth2 (RFC 6749 §5.2), en anglais, jamais une clé i18n interne à BloumeChat :
{
"success": false,
"error": "invalid_grant",
"error_description": "The redirect_uri does not match the one used in the authorization request."
}Codes error possibles :
error | Statut HTTP | Cause |
|---|---|---|
invalid_request | 400 | Paramètre requis manquant (code, client_id, client_secret, access_tokens, app_secret...) |
unsupported_grant_type | 400 | grant_type différent de authorization_code (aucun autre grant n'est supporté, voir plus haut) |
invalid_client | 401 | client_id/client_secret (ou app_secret) incorrect |
invalid_grant | 400 | Code d'autorisation invalide, déjà utilisé, expiré, ou redirect_uri ne correspondant pas |
invalid_token | 401 | Access token invalide ou expiré (/oauth2/resources uniquement) |
server_error | 500 | Erreur inattendue côté BloumeChat — réessayez, et remontez-le si ça persiste |
Vérifiez toujours error (ou le statut HTTP) plutôt que de parser error_description, qui peut évoluer légèrement dans son libellé.
Ajouter un bot ou faire rejoindre un serveur via OAuth2
Pour un flux "Ajouter à un serveur" (comme un bouton "Add Bot" ou "Join Server"), incluez server_id et le(s) scope(s) bot et/ou guilds.join dans l'URL d'autorisation :
https://bloumechat.com/oauth2/authorize
?client_id=VOTRE_CLIENT_ID
&scope=bot
&server_id=PUBLIC_ID_DU_SERVEURSi server_id est omis, l'écran d'autorisation propose un sélecteur de serveur listant uniquement ceux où l'utilisateur a la permission MANAGE_APPLICATIONS (ou est propriétaire/admin) — c'est le comportement utilisé par le portail développeurs BloumeChat lui-même. Aucun redirect_uri n'est nécessaire pour ce flux : l'écran affiche une confirmation de succès directement.
Les deux scopes ne vérifient pas la même identité :
botinstalle le bot sur le serveur — un acte privilégié, donc c'est l'utilisateur qui autorise qui doit avoirMANAGE_APPLICATIONS(ou être propriétaire/admin) sur ce serveur.guilds.joinajoute l'utilisateur qui autorise au serveur — la permission requise porte sur le bot (déjà membre du serveur, avecCREATE_INVITEouADMINISTRATOR), pas sur cet utilisateur. Un visiteur classique de votre site, sans aucun rôle sur le serveur BloumeChat visé, peut donc être auto-rejoint via ce scope du moment que votre bot y a déjà les droits nécessaires.
Sécurité et bonnes pratiques
- Ne jamais exposer
client_secretcôté client — uniquement dans du code serveur, jamais dans une app mobile/SPA sans backend, jamais dans une variableNEXT_PUBLIC_*ou équivalent. - Valider
redirect_uri— n'enregistrez que des URIs HTTPS que vous contrôlez réellement dans le portail développeurs (règle #12 côté BloumeChat : identifiants publics uniquement, jamais d'ID interne exposé). - Ne pas persister l'access token côté client (
localStorage, cookie non-httpOnly) — créez votre propre session applicative après l'échange, comme le fait l'exemple Express ci-dessus. - Le code d'autorisation est à usage unique et expire en 10 minutes — échangez-le immédiatement après réception, ne le mettez pas en file d'attente.
captchaToken— l'écran d'autorisation BloumeChat peut exiger un CAPTCHA adaptatif avant de délivrer le code (protection anti-abus côté BloumeChat) ; cela se gère automatiquement dans l'UI d'autorisation BloumeChat elle-même, vous n'avez rien à implémenter côté application tierce.
Prochaines étapes
- SDK bot — Démarrage rapide — pour un bot temps réel (Socket.IO), pas un login web.
- Permissions — comprendre
MANAGE_APPLICATIONS(requis de l'utilisateur pour le scopebot) etCREATE_INVITE(requis du bot pour le scopeguilds.join), et le bitmask qui les porte. - Portail Développeurs → onglet OAuth2 de votre application — gérer Client ID/Secret et URIs de redirection.
