Uwierzytelnianie członków witryny

Uwierzytelniaj członków witryny przez backendowe mutacje GraphQL siteMember wywoływane przez bramę - rejestracja, logowanie, odświeżanie, wylogowanie, reset hasła i weryfikacja email. Slim SDK nie ma helperów auth; sesją zarządzasz sam.

24 lipca 2026

Przegląd

Członkowie witryny to końcowi użytkownicy opublikowanej strony - osoby, które się rejestrują, logują i mają konto. Członek to rekord modelu treści w Twoim workspace, identyfikowany przez modelSlug (na przykład members) oraz identity (zwykle email). Nie ma specjalnej tabeli użytkowników - członkowie to rekordy.

Uwierzytelnianie członków to działająca funkcja backendu, udostępniona przez przestrzeń mutacji GraphQL siteMember. Wywołujesz ją przez bramę za pomocą createCmssyClient(cmssy).query(). Slim SDK nie dostarcza helperów auth - żadnej trasy auth, middleware ani czytnika sesji. Flow i sesja należą do Ciebie.

Sesją zarządzasz Ty. login i refresh zwracają surowe accessToken i refreshToken - backend nie ustawia ciasteczka. Twoja aplikacja decyduje, gdzie żyją (ciasteczko httpOnly ustawiane przez Twoją trasę to bezpieczny domyślny wybór) i dołącza Authorization: Bearer <accessToken> przy uwierzytelnionych wywołaniach.


1. Klient bramy

Utwórz jedną instancję createCmssyClient i trzymaj obok niej każdy dokument mutacji. Każda mutacja żyje w przestrzeni 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. Rejestracja

register tworzy rekord członka z modelSlug, identity i password; dodatkowe pola profilu trafiają do fields. Zwraca { success, message }, a gdy wymagana jest weryfikacja email, wysyła email weryfikacyjny.

// 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. Zaloguj i zapisz sesję

login zwraca { success, message, accessToken, refreshToken, accessTokenExpiresIn }. Backend nie ustawia ciasteczka - to Twoja trasa zapisuje tokeny. Poniżej trafiają do ciasteczek httpOnly kontrolowanych przez Twoją aplikację.

// 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. Uwierzytelnione żądania

Każda operacja w zakresie członka potrzebuje access tokenu. Odczytaj go ze swojego magazynu i wyślij jako Authorization: Bearer <accessToken>; backend rozwiązuje go do zalogowanego członka. logoutEverywhere to jedno z takich wywołań - unieważnia wszystkie sesje członka.

// 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. Odświeżanie

refresh wymienia refreshToken na świeżą parę tokenów (ten sam kształt co login). Zrotuj oba ciasteczka nowymi wartościami; jeśli się nie powiedzie, wyczyść sesję.

// 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. Wylogowanie

logout unieważnia pojedynczy refreshToken. Wywołaj go, a potem wyczyść własne ciasteczka.

// 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. Reset hasła

forgotPassword(modelSlug, identity) wysyła link resetu i zawsze zgłasza sukces, więc nikt nie sprawdzi, które konta istnieją. resetPassword(token, newPassword) zużywa token z tego linku i ustawia nowe hasło.

// 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. Weryfikacja email

verifyEmail(token) zużywa token z linku weryfikacyjnego. Gdy workspace wymaga weryfikacji, logowanie pozostaje zablokowane do czasu zweryfikowania członka.

// verify email - token comes from the verification link (/verify-email?token=...)
await client.query(VERIFY_EMAIL, { token });

Referencja mutacji

Każda mutacja żyje w przestrzeni siteMember. login i refresh zwracają tokeny; reszta zwraca { success, message }.

MutacjaArgumentyZwraca
registerinput: { modelSlug, identity, password, fields }{ success, message }
logininput: { modelSlug, identity, password }{ success, message, accessToken, refreshToken, accessTokenExpiresIn }
refreshrefreshToken{ success, message, accessToken, refreshToken, accessTokenExpiresIn }
logoutrefreshToken{ success, message }
logoutEverywhere- (wymaga Bearer){ success, message }
forgotPasswordmodelSlug, identity{ success, message }
resetPasswordtoken, newPassword{ success, message }
verifyEmailtoken{ success, message }