Mitglieder-Authentifizierung
Authentifiziere Site-Mitglieder über die Backend-GraphQL-Mutationen siteMember via Gateway - Registrierung, Login, Refresh, Logout, Passwort-Reset und E-Mail-Verifizierung. Das schlanke SDK liefert keine Auth-Helper; die Session gehört dir.
Überblick
Site-Mitglieder sind die Endnutzer deiner veröffentlichten Website - die Personen, die sich registrieren, anmelden und ein Konto besitzen. Ein Mitglied ist ein Record eines Content-Modells in deinem Workspace, identifiziert durch seinen modelSlug (zum Beispiel members) plus eine identity (meist eine E-Mail). Es gibt keine spezielle Nutzertabelle - Mitglieder sind Records.
Mitglieder-Authentifizierung ist ein aktives Backend-Feature, bereitgestellt über die siteMember-GraphQL-Mutations-Namespace. Du rufst sie über das Gateway mit createCmssyClient(cmssy).query() auf. Das schlanke SDK liefert keine Auth-Helper - keine Auth-Route, keine Middleware, keinen Session-Reader. Der Flow und die Session gehören dir.
Die Session gehört dir. login und refresh geben die rohen accessToken und refreshToken zurück - das Backend setzt kein Cookie. Deine App entscheidet, wo sie leben (ein httpOnly-Cookie, das deine Route setzt, ist der sichere Standard) und hängt bei authentifizierten Aufrufen Authorization: Bearer <accessToken> an.
1. Der Gateway-Client
Erstelle eine createCmssyClient-Instanz und halte jedes Mutations-Dokument daneben. Jede Mutation lebt im siteMember-Namespace.
// 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. Registrieren
register erstellt einen Mitglieds-Record aus modelSlug, identity und password; zusätzliche Profilfelder kommen in fields. Es gibt { success, message } zurück und sendet, wenn E-Mail-Verifizierung erforderlich ist, eine Verifizierungs-E-Mail.
// 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. Anmelden und die Session speichern
login gibt { success, message, accessToken, refreshToken, accessTokenExpiresIn } zurück. Das Backend setzt kein Cookie - deine Route speichert die Tokens. Unten wandern sie in httpOnly-Cookies, die deine App kontrolliert.
// 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. Authentifizierte Requests
Jede mitglieds-scoped Operation braucht den Access-Token. Lies ihn aus deinem Speicher und sende ihn als Authorization: Bearer <accessToken>; das Backend löst ihn zum angemeldeten Mitglied auf. logoutEverywhere ist so ein Aufruf - er widerruft jede Session des Mitglieds.
// 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 tauscht einen refreshToken gegen ein frisches Token-Paar (dieselbe Form wie login). Rotiere beide Cookies mit den neuen Werten; schlägt es fehl, lösche die 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. Abmelden
logout widerruft einen einzelnen refreshToken. Rufe ihn auf und lösche dann deine eigenen 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. Passwort-Reset
forgotPassword(modelSlug, identity) mailt einen Reset-Link und meldet immer Erfolg, sodass niemand prüfen kann, welche Konten existieren. resetPassword(token, newPassword) verbraucht den Token aus diesem Link und setzt das neue Passwort.
// 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. E-Mail-Verifizierung
verifyEmail(token) verbraucht den Token aus dem Verifizierungslink. Wenn ein Workspace Verifizierung verlangt, bleibt die Anmeldung blockiert, bis das Mitglied verifiziert ist.
// verify email - token comes from the verification link (/verify-email?token=...)
await client.query(VERIFY_EMAIL, { token });Mutations-Referenz
Jede Mutation lebt im siteMember-Namespace. login und refresh geben Tokens zurück; der Rest gibt { success, message } zurück.
| Mutation | Argumente | Rückgabe |
|---|---|---|
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 | - (braucht Bearer) | { success, message } |
forgotPassword | modelSlug, identity | { success, message } |
resetPassword | token, newPassword | { success, message } |
verifyEmail | token | { success, message } |