Authentification des membres
Authentifiez les membres du site via les mutations GraphQL backend siteMember appelées par la gateway - inscription, connexion, refresh, déconnexion, réinitialisation du mot de passe et vérification d'e-mail. Le SDK slim ne fournit aucun helper d'auth ; vous gérez la session.
Aperçu
Les membres du site sont les utilisateurs finaux de votre site publié - les personnes qui s'inscrivent, se connectent et possèdent un compte. Un membre est un enregistrement d'un modèle de contenu dans votre workspace, identifié par son modelSlug (par exemple members) plus une identity (généralement un e-mail). Il n'y a pas de table utilisateurs spéciale - les membres sont des enregistrements.
L'authentification des membres est une fonctionnalité backend active, exposée via le namespace de mutations GraphQL siteMember. Vous l'appelez via la gateway avec createCmssyClient(cmssy).query(). Le SDK slim ne fournit aucun helper d'auth - pas de route d'auth, pas de middleware, pas de lecteur de session. Le flux et la session vous appartiennent.
Vous gérez la session. login et refresh renvoient les accessToken et refreshToken bruts - le backend ne pose aucun cookie. Votre app décide où ils vivent (un cookie httpOnly posé par votre route est le choix sûr par défaut) et attache Authorization: Bearer <accessToken> sur les appels authentifiés.
1. Le client de la gateway
Créez une instance createCmssyClient et gardez chaque document de mutation à côté. Chaque mutation vit sous le namespace siteMember.
// lib/cmssy-members.ts
import { createCmssyClient } from "@cmssy/react";
import { cmssy } from "@/cmssy.config";
// One gateway client. Every siteMember mutation goes through client.query().
export const client = createCmssyClient(cmssy);
export const REGISTER = `mutation Register($input: SiteMemberRegisterInput!) {
siteMember { register(input: $input) { success message } }
}`;
export const LOGIN = `mutation Login($input: SiteMemberLoginInput!) {
siteMember {
login(input: $input) {
success
message
accessToken
refreshToken
accessTokenExpiresIn
}
}
}`;
export const REFRESH = `mutation Refresh($refreshToken: String!) {
siteMember {
refresh(refreshToken: $refreshToken) {
success
message
accessToken
refreshToken
accessTokenExpiresIn
}
}
}`;
export const LOGOUT = `mutation Logout($refreshToken: String!) {
siteMember { logout(refreshToken: $refreshToken) { success message } }
}`;
export const LOGOUT_EVERYWHERE = `mutation LogoutEverywhere {
siteMember { logoutEverywhere { success message } }
}`;
export const FORGOT_PASSWORD = `mutation ForgotPassword($modelSlug: String!, $identity: String!) {
siteMember { forgotPassword(modelSlug: $modelSlug, identity: $identity) { success message } }
}`;
export const RESET_PASSWORD = `mutation ResetPassword($token: String!, $newPassword: String!) {
siteMember { resetPassword(token: $token, newPassword: $newPassword) { success message } }
}`;
export const VERIFY_EMAIL = `mutation VerifyEmail($token: String!) {
siteMember { verifyEmail(token: $token) { success message } }
}`;2. Inscription
register crée un enregistrement de membre à partir de modelSlug, identity et password ; les champs de profil supplémentaires vont dans fields. Il renvoie { success, message } et, quand la vérification d'e-mail est requise, envoie un e-mail de vérification.
// app/api/auth/register/route.ts
import { client, REGISTER } from "@/lib/cmssy-members";
export async function POST(request: Request) {
const { email, password, name } = await request.json();
const { siteMember } = await client.query(REGISTER, {
input: {
modelSlug: "members",
identity: email,
password,
fields: { name },
},
});
return Response.json(siteMember.register); // { success, message }
}3. Connexion et stockage de la session
login renvoie { success, message, accessToken, refreshToken, accessTokenExpiresIn }. Le backend ne pose pas de cookie - c'est votre route qui stocke les tokens. Ci-dessous, ils vont dans des cookies httpOnly que votre app contrôle.
// app/api/auth/login/route.ts
import { cookies } from "next/headers";
import { client, LOGIN } from "@/lib/cmssy-members";
export async function POST(request: Request) {
const { email, password } = await request.json();
const { siteMember } = await client.query(LOGIN, {
input: { modelSlug: "members", identity: email, password },
});
const res = siteMember.login;
if (!res.success || !res.accessToken) {
return Response.json({ ok: false, message: res.message }, { status: 401 });
}
// You own the session - store the raw tokens yourself. The backend sets no cookie.
const jar = await cookies();
jar.set("member_access", res.accessToken, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
maxAge: res.accessTokenExpiresIn ?? 900,
});
jar.set("member_refresh", res.refreshToken, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
});
return Response.json({ ok: true });
}4. Requêtes authentifiées
Toute opération à portée membre a besoin du token d'accès. Lisez-le depuis votre stockage et envoyez-le en Authorization: Bearer <accessToken> ; le backend le résout en membre connecté. logoutEverywhere est un tel appel - il révoque toutes les sessions du membre.
// app/api/auth/logout-everywhere/route.ts
import { cookies } from "next/headers";
import { client, LOGOUT_EVERYWHERE } from "@/lib/cmssy-members";
export async function POST() {
const accessToken = (await cookies()).get("member_access")?.value;
if (!accessToken) return Response.json({ ok: false }, { status: 401 });
// Authenticated call: attach the access token as a Bearer header.
const { siteMember } = await client.query(LOGOUT_EVERYWHERE, {}, {
headers: { Authorization: `Bearer ${accessToken}` },
});
return Response.json(siteMember.logoutEverywhere);
}5. Refresh
refresh échange un refreshToken contre une nouvelle paire de tokens (la même forme que login). Faites tourner les deux cookies avec les nouvelles valeurs ; en cas d'échec, effacez la session.
// app/api/auth/refresh/route.ts
import { cookies } from "next/headers";
import { client, REFRESH } from "@/lib/cmssy-members";
export async function POST() {
const jar = await cookies();
const refreshToken = jar.get("member_refresh")?.value;
if (!refreshToken) return Response.json({ ok: false }, { status: 401 });
const { siteMember } = await client.query(REFRESH, { refreshToken });
const res = siteMember.refresh;
if (!res.success || !res.accessToken) {
jar.delete("member_access");
jar.delete("member_refresh");
return Response.json({ ok: false }, { status: 401 });
}
// refresh rotates the pair - overwrite both cookies with the new tokens.
jar.set("member_access", res.accessToken, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
maxAge: res.accessTokenExpiresIn ?? 900,
});
jar.set("member_refresh", res.refreshToken, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
});
return Response.json({ ok: true });
}6. Déconnexion
logout révoque un seul refreshToken. Appelez-le, puis effacez vos propres cookies.
// app/api/auth/logout/route.ts
import { cookies } from "next/headers";
import { client, LOGOUT } from "@/lib/cmssy-members";
export async function POST() {
const jar = await cookies();
const refreshToken = jar.get("member_refresh")?.value;
if (refreshToken) {
await client.query(LOGOUT, { refreshToken });
}
jar.delete("member_access");
jar.delete("member_refresh");
return Response.json({ ok: true });
}7. Réinitialisation du mot de passe
forgotPassword(modelSlug, identity) envoie un lien de réinitialisation et signale toujours un succès, pour que personne ne puisse sonder quels comptes existent. resetPassword(token, newPassword) consomme le token de ce lien et pose le nouveau mot de passe.
// forgot password - always reports success (no account enumeration)
await client.query(FORGOT_PASSWORD, { modelSlug: "members", identity: email });
// reset password - token comes from the emailed link (/reset-password?token=...)
await client.query(RESET_PASSWORD, { token, newPassword });8. Vérification d'e-mail
verifyEmail(token) consomme le token du lien de vérification. Quand un workspace exige la vérification, la connexion reste bloquée jusqu'à ce que le membre soit vérifié.
// verify email - token comes from the verification link (/verify-email?token=...)
await client.query(VERIFY_EMAIL, { token });Référence des mutations
Chaque mutation vit sous le namespace siteMember. login et refresh renvoient des tokens ; le reste renvoie { success, message }.
| Mutation | Arguments | Renvoie |
|---|---|---|
register | input: { modelSlug, identity, password, fields } | { success, message } |
login | input: { modelSlug, identity, password } | { success, message, accessToken, refreshToken, accessTokenExpiresIn } |
refresh | refreshToken | { success, message, accessToken, refreshToken, accessTokenExpiresIn } |
logout | refreshToken | { success, message } |
logoutEverywhere | - (requiert Bearer) | { success, message } |
forgotPassword | modelSlug, identity | { success, message } |
resetPassword | token, newPassword | { success, message } |
verifyEmail | token | { success, message } |