17. modulAuth, session és tenant-identitás
Cloudflare for Devs · 17. modul

Auth, session és tenant-identitás

Nincs express-session, nincs passport, és a memóriában tartott session-store sem működik. Ez a modul végigveszi, hogyan lesz mégis bejelentkezés, jogosultság és tenant-azonosítás egy Workers-alapú multitenant SaaS-ban — a session tárolásától az enterprise SSO-ig.

17.1Miért más ez itt?

A Node-világban a session-kezelés jellemzően így nézett ki: a szerver memóriájában (vagy Redisben) van egy session-store, a kliens kap egy session-ID-t cookie-ban, és minden kéréskor kikeresed. Workersön ebből két dolog törik el:

Ebből következik a modul központi döntése: hol él a session állapota?

17.2A négy session-stratégia

StratégiaHogyanElőnyHátrány
1. Aláírt/titkosított cookie
(stateless)
a session-adat maga a cookie-ban van, kriptográfiailag lezárva (sealed); a szerver csak felnyitja nulla lekérdezés, tökéletesen edge-barát, nincs mit skálázni méretkorlát (~4 KB), és nem lehet azonnal visszavonni (a kiadott cookie a lejáratáig él)
2. KV-alapú session cookie-ban csak az ID; az adat KV-ben, globálisan replikálva gyors olvasás mindenhonnan, nagy adat is elfér, kilistázható eventually consistent (6. modul): a visszavonás ~60 s-ig „átcsúszhat"
3. DB-alapú session (D1/Postgres) klasszikus session-tábla azonnali visszavonás, auditálható, tranzakcionális minden kérés egy DB-kör (a primary régióba — 5b. modul!)
4. Durable Object per user a session-állapot egy címezhető DO-ban erős konzisztencia, azonnali kiléptetés, élő kapcsolatkezelés komplexebb; a DO helye számít (7. modul)
Az ajánlott felállás egy SaaS-hoz — hibrid: a rövid életű access-session aláírt cookie-ban (1), rövid lejárattal (pl. 15–60 perc), így a kérések 99%-a nulla lekérdezéssel authentikál; mellette egy hosszú életű refresh-token a DB-ben (3), ami visszavonható. Kijelentkezéskor/kitiltáskor a refresh-tokent öld meg — az access-session a rövid lejárat miatt magától elévül. Ez a minta adja az edge sebességét úgy, hogy a visszavonhatóság sem vész el. Ha azonnali, globális kiléptetés kell (pl. biztonsági incidens), tegyél egy „revocation epoch" számlálót a userhez KV-be vagy DO-ba, és a cookie-ban tárold, melyik epochhal készült.

Aláírt cookie a gyakorlatban (Nuxt)

Nuxtban a nuxt-auth-utils pontosan ezt a mintát adja: lezárt (sealed) cookie-session, ami a Workers-runtime-ban is működik, mert csak Web Crypto kell hozzá.

server/api/auth/login.post.ts
export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event);
  const user = await verifyCredentials(event, email, password);   // lásd 17.4
  if (!user) throw createError({ statusCode: 401 });

  await setUserSession(event, {
    user:   { id: user.id, email: user.email },
    tenant: { id: user.tenant_id, role: user.role },   // a tenant a session RÉSZE
    loggedInAt: new Date().toISOString(),
  });
  return { ok: true };
});

A titok a NUXT_SESSION_PASSWORD (min. 32 karakter), ami secretként él (8. és 12. modul): .dev.vars lokálisan, wrangler secret put élesben.

17.3Jelszó-hashelés: mi működik a Workersön?

Itt van egy fontos runtime-korlát, amivel Node-ból érkezve szembe fogsz futni. A bcrypt natív bináris — nem fut isolate-ben (2. modul). Az argon2 ugyanígy. Ami működik:

MegoldásÁllapotMegjegyzés
PBKDF2 a Web Crypto API-valnatívan, gyorsana platform beépített, hardveresen gyorsított implementációja; magas iterációszámmal ma is elfogadott
scrypt / bcrypt WASM-portbanműködikde figyelj a CPU-limitre (2. modul): a szándékosan lassú hash pont a CPU-t eszi
bcrypt / argon2 natívnemnatív bináris — Containers kellene hozzá
Külső auth-szolgáltatómegkerülödha nem akarsz jelszót kezelni: OAuth/OIDC szolgáltató (lásd 17.6)
server/utils/password.ts — PBKDF2 Web Cryptóval
const ITER = 210_000;   // igazítsd a CPU-budgetedhez, mérd meg! (9. modul)

export async function hashPassword(password: string) {
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const key = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits"]);
  const bits = await crypto.subtle.deriveBits(
    { name: "PBKDF2", salt, iterations: ITER, hash: "SHA-256" }, key, 256);

  return `pbkdf2$${ITER}$${b64(salt)}$${b64(new Uint8Array(bits))}`;
}

export async function verifyPassword(stored: string, password: string) {
  const [, iter, salt, hash] = stored.split("$");
  // … ugyanaz a derive, majd IDŐÁLLANDÓ összehasonlítás:
  return timingSafeEqual(computed, hash);
}
Migrációs csapda: ha a meglévő adatbázisodban bcrypt-hashelt jelszavak vannak, azokat nem tudod „átkonvertálni" (a hash egyirányú). A bevált minta: a régi hasheket egy WASM-bcrypttel ellenőrzöd bejelentkezéskor, és sikeres belépéskor újrahasheled PBKDF2-vel. Néhány hónap alatt a felhasználóid nagy része átáll magától; a maradékot jelszó-visszaállításra kéred.

17.4A tenant-identitás: hol dől el, ki melyik tenant tagja?

Multitenant SaaS-ban az auth nem ér véget a „ki vagy?" kérdésnél — a lényeg a „melyik tenant nevében, milyen szerepkörrel?". Három forrásból jöhet a tenant, és ezt tudatosan kell eldönteni:

ForrásMikorKockázat
Aldomain / custom domain
acme.app.com
ha minden tenant saját hostot kap (16. modul: Cloudflare for SaaS)alacsony — a host nem hamisítható a session mellett
Session (a bejelentkezéskor rögzített tenant)ha egy user egy tenanthoz tartozikalacsony — szerveroldalról jön
Kérés-paraméter (path, header)ha egy user több tenantban is tagmagas — mindig ellenőrizni kell a tagságot

A helyes minta: a tenant-feloldás egyetlen middleware-ben történjen, egyszer, és onnantól a kontextusból jöjjön — soha ne minden endpoint oldja meg újra (13. modul elve):

server/middleware/01.tenant.ts
export default defineEventHandler(async (event) => {
  if (!getRequestURL(event).pathname.startsWith("/api/")) return;

  const session = await getUserSession(event);
  if (!session?.user) return;                       // publikus végpont

  // 1) a hostból megpróbáljuk (custom domain / aldomain)
  const hostTenant = await tenantFromHost(event);

  // 2) ha van host-tenant, ellenőrizzük a TAGSÁGOT — ez a kulcslépés
  const tenantId = hostTenant?.id ?? session.tenant.id;
  if (hostTenant && hostTenant.id !== session.tenant.id) {
    const member = await useDb(event).prepare(
      "SELECT role FROM memberships WHERE user_id = ? AND tenant_id = ?"
    ).bind(session.user.id, hostTenant.id).first();
    if (!member) throw createError({ statusCode: 403 });
    event.context.role = member.role;
  } else {
    event.context.role = session.tenant.role;
  }

  event.context.tenantId = tenantId;                // innentől EZT használja mindenki
  event.context.userId   = session.user.id;
});
A klasszikus multitenant sebezhetőség (IDOR) így néz ki: az endpoint elhiszi a kérésben érkező tenantId-t, vagy a lekérdezésből kimarad a tenant-feltétel. Két védelem: ① a tenant soha ne a kliens kéréséből jöjjön szűrés nélkül; ② az adathozzáférés menjen egy vékony rétegen át, ami mindig beteszi a tenant-szűrést. A 13. modul server/utils mintája erre való: ha a useDb() helyett egy tenantQuery(event, sql, params) segédet használsz, ami automatikusan hozzáfűzi a tenant_id = ? feltételt, akkor a hibalehetőség egyetlen fájlba szorul. Írj rá tesztet is (11. modul): „A tenant usere nem éri el B tenant rekordját" — ez az egyik legfontosabb tesztesetetek.

17.5Könyvtárak: mi működik a workerd-ben?

MegoldásModellWorkers-alkalmasság
nuxt-auth-utilslezárt cookie-session, minimalista; a szolgáltatói OAuth-folyamatokat is adjajó — Web Crypto alapú, kevés függőség
better-authteljes auth-keretrendszer (email/jelszó, OAuth, 2FA, szervezetek/tagságok, session-kezelés), adapterekkeljó — D1/KV-vel bevált; Nuxt-modul is van hozzá
Saját JWT/cookiea 17.2–17.3 minták kézzelteljes kontroll, de te felelsz a kriptó-részletekért
Passport / express-sessionNode-középpontú middleware-láncnem — Express-alapú, Node API-kra épül
Külső szolgáltató (Auth0, Clerk, WorkOS, Keycloak…)OIDC/OAuth kívülrőlműködik — a Worker csak tokent validál (JWKS)
Ajánlás a te helyzetedre: ha meglévő SaaS-t migrálsz saját user-táblával, a legkisebb kockázat a saját, egyszerű cookie-session (nuxt-auth-utils) + a meglévő user/jelszó-logikád átemelése (a hash-migrációval, 17.3). A better-auth akkor jó választás, ha úgyis újraírnád az auth-réteget, és kellenek a szervezet/tagság/2FA funkciók készen. Külső szolgáltató akkor, ha az enterprise SSO gyorsan kell és nem akarsz SAML-t implementálni (17.6). Amit kerülj: bármi, ami Express-middleware-t vagy natív bináris hashet feltételez.

17.6Enterprise SSO — amikor az ügyfél SAML-t kér

Egy B2B SaaS életében biztosan eljön a pillanat, amikor egy nagy ügyfél azt mondja: „a mi felhasználóink az Okta/Entra ID/Google Workspace fiókjukkal lépjenek be". Négy út van:

  1. OIDC magadtól. Ha az ügyfél OIDC-t (nem SAML-t) tud, ez teljesen kivitelezhető Workersen: authorization code flow + PKCE, a JWKS-t cache-eled KV-ben, a tokent Web Cryptóval validálod. Tenantonként tárolod az issuer/client_id/secret hármast.
  2. SAML külső szolgáltatóval. A SAML XML-alapú, aláírás-ellenőrzéssel — implementálható, de nem szórakoztató, és biztonsági szempontból hibázni könnyű. Erre valók a dedikált szolgáltatók (pl. WorkOS-jellegű SSO-brókerek): ők beszélnek SAML-t az ügyféllel, neked OIDC-t vagy egyszerű API-t adnak.
  3. Teljes auth-platform (Auth0/Clerk/…): mindent ők visznek, te tokent validálsz.
  4. Cloudflare Access: ez a belső eszközeidre való (admin, staging — 9. modul), nem a végfelhasználói SSO-ra. Ne keverd össze a kettőt.

Amit az architektúrádban előre készíts elő, még mielőtt az első ügyfél kéri: a tenants táblában legyen hely az SSO-konfignak (sso_type, issuer, client_id, domain), és a login-folyamat tudjon email-domain alapján irányítani: ha valaki @acme.com címmel próbál belépni, és az acme tenantnál SSO van bekapcsolva, a jelszavas belépés helyett az ő identity providerükhöz kell átdobni. Ez a „home realm discovery", és utólag beépíteni sokkal fájdalmasabb.

17.7Gép-gép: API-kulcsok és service-to-service

17.8Checklist — mielőtt élesben auth-olsz

17.9Ellenőrizd magad

  1. Miért nem használható a megszokott memória- vagy Redis-alapú session-store Workersön, és mi az ajánlott hibrid?
    Válasz

    Nincs megbízható processz-memória (az isolate eldobható, és sok példányban fut), a központi store pedig elveszi az edge latencia-előnyét. Ajánlott: rövid lejáratú aláírt cookie-session (nulla lekérdezés) + hosszú életű, visszavonható refresh-token a DB-ben; azonnali globális kiléptetéshez revocation epoch.

  2. bcrypt-hashelt jelszavaid vannak. Mi a migrációs terv?
    Válasz

    A natív bcrypt nem fut isolate-ben. A régi hasheket WASM-bcrypttel ellenőrzöd belépéskor, és sikeres belépéskor újrahasheled PBKDF2-vel (Web Crypto). A maradék felhasználót idővel jelszó-visszaállításra kéred.

  3. Hol dőljön el, melyik tenant nevében fut egy kérés — és mi a tipikus sebezhetőség?
    Válasz

    Egyetlen middleware-ben, a hostból és/vagy a session-ből, tagság-ellenőrzéssel; onnantól a kontextusból. A tipikus hiba (IDOR), hogy az endpoint elhiszi a kérésben érkező tenant-azonosítót, vagy kimarad a tenant-szűrés a lekérdezésből — ezért kell egy központi adathozzáférési réteg, ami mindig hozzáteszi.

  4. Egy ügyfél SAML SSO-t kér. Mik a lehetőségeid, és mit érdemes előre beépíteni?
    Válasz

    Lehetőségek: saját OIDC (ha az ügyfél tudja), SSO-bróker szolgáltató a SAML-hez, vagy teljes auth-platform. Előre építsd be: SSO-konfigmezők a tenants táblába, és email-domain alapú irányítás a login-folyamatba (home realm discovery) — utólag ez fájdalmas.

  5. Hogyan tárolod az ügyfeleidnek kiadott API-kulcsokat?
    Válasz

    Csak a hashüket a DB-ben (a nyers kulcsot egyszer mutatod meg), felismerhető prefixszel, a tenanthoz és jogosultságokhoz kötve, lejárattal; ellenőrzéskor a beérkező kulcsot hasheled. Belső Worker→Worker híváshoz viszont ne kulcs, hanem service binding.

Előző16. modul — A platform teljes szolgáltatás-térképe Következő 18. modul — Cache és teljesítmény