Brak wyników.

SSO & Sesje

Co naprawdę dzieje się między klikiem login a renderem dashboardu.

SSO & Sesje

Przestań traktować OIDC jak black box. Ta strona przechodzi przez cały taniec loginu — co jest na kablu, co w cookies, jak jeden login w WolfieAuth daje user-owi jednoczesny dostęp do każdej apki którą zarejestrowałeś.

OIDC handshake (PKCE flow)

┌─────────────┐                                      ┌──────────────────────┐
│   User      │                                      │   WolfieAuth         │
│  Browser    │                                      │ auth.wolfieguard.com │
└──────┬──────┘                                      └───────────┬──────────┘
       │                                                         │
       │  1. GET /auth/login   (twoja apka)                      │
       │ ───────────────────▶                                    │
       │                                                         │
       │  2. 302 → /authorize?response_type=code                 │
       │       &client_id=<twojaorg>-<myapp>                     │
       │       &redirect_uri=<twoj-callback>                     │
       │       &scope=openid profile email org                   │
       │       &code_challenge=<sha256-of-verifier>              │
       │       &state=<csrf-token>                               │
       │ ◀──────────────────────────────────────                 │
       │                                                         │
       │  3. GET /authorize?... (WolfieAuth)                     │
       │ ──────────────────────────────────────▶                 │
       │                                                         │
       │  4. Renderuje brand-owany login page                    │
       │ ◀──────────────────────────────────────                 │
       │                                                         │
       │  5. POST email + password                               │
       │ ──────────────────────────────────────▶                 │
       │                                                         │
       │  6. 302 → <twoj-callback>?code=ABC&state=<csrf>         │
       │ ◀──────────────────────────────────────                 │
       │                                                         │
       │  7. GET <twoj-callback>?code=ABC                        │
       │ ───────────────────▶                                    │
       │                                                         │
       │       (twoja apka POSTuje do WolfieAuth /token)         │
       │       grant_type=authorization_code                     │
       │       code=ABC                                          │
       │       code_verifier=<oryginalny-PKCE-verifier>          │
       │                                                         │
       │       ◀── { access_token, id_token, refresh_token } ──  │
       │                                                         │
       │       (twoja apka waliduje id_token signature           │
       │        przeciwko /.well-known/jwks.json)                │
       │                                                         │
       │       (twoja apka GETuje /userinfo z access_token       │
       │        żeby pobrać claims do session cookie)            │
       │                                                         │
       │  8. 302 → /dashboard + Set-Cookie: session=<sealed>     │
       │ ◀───────────────────                                    │
       │                                                         │
       │  9. Kolejne requesty niosą session cookie               │
       │       apka czyta cookie, parsuje claims, renderuje      │
       └─────────────────────────────────────────────────────────┘

Kluczowe punkty:

  • PKCE chroni przed code interception. code_verifier generowany client-side, nigdy nie wysyłany aż do /token exchange. Atakujący kradnący URL z ?code=ABC nie wymieni go na tokeny bez verifier-a.
  • State to Twoja CSRF defence. Generuj losowy nonce na /auth/login, zachowaj w sealed cookie, porównaj na callback. Każdy SDK to robi za Ciebie.
  • Tokeny nigdy nie idą do przeglądarki. Tylko sealed session cookie dociera do przeglądarki user-a. access_token / id_token / refresh_token zostają server-side w session store SDK-a.
  • Brak hit-u DB per request. id_token to signed JWT. Validate przeciwko JWKS endpoint przy boocie (cache in-process), potem trust contents na każdym kolejnym request.

Czynniki uwierzytelnienia podczas handshake

Krok 5 w diagramie powyżej (“POST email + password”) to skrót. Login screen WolfieAuth oferuje cztery czynniki które user może mieszać i łączyć — Twoja apka nie widzi różnicy. Cokolwiek user wybierze, Twoja apka dostaje ten sam id_token na końcu:

  • Hasło. Klasyk. Bcrypt-hashed at rest, rate-limited per IP i per email. Fallback gdy nic innego nie skonfigurowane.
  • Passkeys / WebAuthn. Hardware-backed, phishing-resistant. Jeśli user zarejestrował passkey na urządzeniu, login screen prosi o niego zamiast (albo dodatkowo do) hasła. End-to-end passwordless.
  • Magic links. Podpisany URL emailowany do user-a. One-shot, szybko wygasa. Pełni rolę MFA — udowodnienie kontroli nad emailem liczy się jako drugi czynnik.
  • TOTP. Sześciocyfrowe kody z dowolnego authenticator app (1Password, Bitwarden, Aegis, Authy). Wymagane gdy user (albo plan flag requireTwoFactor Twojej apki) je włączy. Recovery codes wystawiane przy enrolment-ie do lockout-recovery.

User może zarejestrować wiele czynników i login screen wybiera najlepszy dostępny. Recovery codes są single-use i regenerowalne z /account/security.

Co widzi Twoja apka: id_token niesie amr (Authentication Methods References) array wskazujący które czynniki zadziałały — np. ["pwd", "mfa"] albo ["webauthn"]. Jeśli chcesz wymusić fresh strong-factor login dla wrażliwej akcji (re-auth przed zmianą metody płatności), redirectuj do /authorize?prompt=login&max_age=0 i user dostaje password / passkey prompt znowu.

SDK seal-uje (encrypt + HMAC) mały JSON object do jednego cookie:

{
  "sub":            "clxxx...",
  "email":          "[email protected]",
  "name":           "Pawel Wolfie",
  "role":           "USER",
  "wolfieauth_org_id":     "org_abc",
  "wolfieauth_org_slug":   "acme",
  "wolfieauth_membership_kind":     "MEMBER",
  "wolfieauth_membership_approval": "APPROVED",
  "wolfieauth_role_slug":  "editor",
  "wolfieauth_permissions": ["invoices.read", "invoices.write"],
  "wolfieauth_plans":    [...],
  "wolfieauth_features": ["wolfie-wolfiecrm:ksef_enabled", ...],
  "exp":            1768432200
}

Cookie jest httpOnly + secure + sameSite=lax. Nie da się go przeczytać z JavaScriptu (brak XSS exfiltration). Nowy login refresh-uje go. Logout czyści.

Org scope w tokenach — multi-tenancy w warstwie auth

Claim wolfieauth_org_id to najważniejsze pole dla każdej multi-tenant apki i to które developerzy najczęściej przeoczają. Każdy token jest scopowany do jednej orgi — nawet gdy user należy do kilku. Robione celowo: pozwala Twoim DB queries używać WHERE org_id = $claim prosto z tokenu bez zastanawiania się “ale na którą org user akurat patrzy?”

Jak wybierana jest org:

  1. homeOrgId user-a to default — org do której się zarejestrował albo był zaproszony jako primary.
  2. User może zmienić aktywną orgę z menu konta (/account/orgs); kolejny mint OIDC tokenu odzwierciedla nową aktywną orgę.
  3. SDK-i akceptują ?orgId=… na URL startu SSO żeby wymusić konkretną orgę dla tego logowania. Przydatne gdy Twoja apka już wie którego tenanta user dotyczy (sub-domain routing, bookmark z org slug w URL).

Co płynie z aktywnej orgi:

  • wolfieauth_org_id, wolfieauth_org_slug, wolfieauth_org_parentId (gdy org siedzi pod resellerem).
  • wolfieauth_role_slug i wolfieauth_permissions[] — rola user-a w tej orgi. Ten sam user może być admin w org A i viewer w org B.
  • wolfieauth_plans i wolfieauth_features — plan który ta org subskrybuje dla Twojej apki. Dwie orgi używające Twojej apki obok siebie mogą mieć różne feature setty.
  • wolfieauth_membership_kind (MEMBER / GUEST / SPECIAL_ADMIN) i wolfieauth_membership_approval.

Jeśli user należy do wielu org i Twoja apka wspiera “org switcher”, wywołaj GET /api/auth/me?orgId=<inna-org-id> żeby wybić fresh session przeciwko tej orgi bez forsowania pełnego OIDC re-handshake. Helpery SDK-ów eksponują to jako switchOrg(orgId) (React, SvelteKit) albo Auth::switchOrg($id) (Laravel).

Cross-app SSO

Cały sens posiadania JEDNEGO WolfieAuth z N zarejestrowanymi apkami: user loguje się raz, jego przeglądarka trzyma cookie na auth.wolfieguard.com, każda inna apka na innym domenie może zakończyć swoje OIDC handshake silently (bez UI).

1. User loguje się w app1.com → cookie na auth.wolfieguard.com
2. User klika "Sign in" w app2.com (inny domen)
3. app2.com redirectuje na auth.wolfieguard.com/authorize
4. WolfieAuth widzi wciąż-ważne SSO cookie, skipuje password prompt
5. Redirectuje z powrotem na app2.com/callback z fresh code
6. app2.com wymienia code na tokeny, ustawia SWOJE session cookie

User postrzega to jako “dwa kliki i jestem w środku” zamiast “wpisz hasło ponownie”. Claim linked_accounts[] mówi Twojej apce do których innych apek ten user się uwierzytelnił:

"linked_accounts": {
  "wolfie-perfex-main": { "external_user_id": "1",  "external_email": "...", "role": "admin" },
  "wolfie-vendor-wolfieguard": { "external_user_id": "42", "external_email": "...", "role": "administrator" }
}

Po co to istnieje: gdy user loguje się do Twojej apki przez WolfieAuth, Twoja apka pewnie ma własne wewnętrzne user ID (row w wp_users, tblstaff, users, cokolwiek). To mapowanie — “WolfieAuth user clxxx… to wewnętrzny user 42 w tej instalce Perfex” — jest zapisywane raz po stronie WolfieAuth jako row LinkedAccount, i ląduje w każdym kolejnym tokenie jako linked_accounts[twoj-client-id].external_user_id. Twoja apka czyta to z JWT claim, znajduje lokalny row po integer ID i pomija email-matching dance przy każdym logowaniu.

Jeśli potrzebujesz pobrać to samo mapowanie dla innych zarejestrowanych apek (np. CRM chce znać WordPress user ID dla tej samej osoby), czytasz z tego samego linked_accounts map — keyed po client_id. Tak workflow cross-app typu “stwórz fakturę w Perfex z zamówienia WooCommerce” stitchuje tożsamości bez ekstra lookup service-u.

Logout — RP-initiated end-session

Gdy user klika Logout w Twojej apce, MUSISZ przekierować go przez WolfieAuth /end_session endpoint, nie tylko wyczyścić lokalny cookie. Inaczej WolfieAuth cookie na auth.wolfieguard.com zostaje żywe i następny login skipuje password prompt — myli i jest niebezpieczne na shared devices.

// Każdy SDK to robi w swoim /auth/logout route
GET /auth/logout
   wyczyść lokalny session cookie
   302 do https://auth.wolfieguard.com/session/end?id_token_hint=<id_token>&post_logout_redirect_uri=https://twoja-apka.com/

User ląduje z powrotem na homepage, w pełni wylogowany z OBYDWOCH (Twojej apki I WolfieAuth).

Token refresh

Jeśli wymagałeś offline_access scope, dostałeś refresh_token razem z innymi. Użyj go żeby wybić fresh access/id tokeny bez forsowania user-a do re-auth:

POST https://auth.wolfieguard.com/token
  grant_type=refresh_token
  refresh_token=<the-token>

Większość SDK robi to automatycznie — wykrywa near-expired session i silently refreshes w tle. Refresh tokeny same się rotują (każde użycie daje nowego), więc skradziony refresh token ma wąskie okno przed unieważnieniem.

Custom claimy przez per-app JWT templates

Czasami standardowe nazwy claimów nie matchują tego co downstream service oczekuje — Hasura chce https://hasura.io/jwt/claims, PostgREST chce role, edge function expects tenant, etc. Zamiast forsować każdą apkę do re-shape claims przy konsumpcji, możesz skonfigurować JWT template na swoim OidcClient który injectuje custom claimy przy mint-time tokenu.

W panelu admin otwórz /admin/clients/<twoja-apka>/jwt-templates. Drop in JSON template z Mustache-style interpolacją:

{
  "https://hasura.io/jwt/claims": {
    "x-hasura-user-id": "{{sub}}",
    "x-hasura-org-id": "{{wolfieauth_org_id}}",
    "x-hasura-default-role": "{{wolfieauth_role_slug}}",
    "x-hasura-allowed-roles": ["admin", "editor", "viewer"]
  }
}

Każdy claim dostępny dla user-a (każde pole wolfieauth_* plus standard OIDC) może być podstawiony. Obok edytora jest Preview button który renderuje template przeciwko Twoim własnym aktualnym claimom — wklej, klik preview, zobacz dokładnie co Twoja apka dostanie przed zapisaniem. Templaty są walidowane na balans {{…}} i rozmiar przy write-time żeby literówka nie mintowała silently broken tokenów.

To też jest odpowiedź na “chcę żeby moja apka dostawała groups: [...] zamiast wolfieauth_role_slug” — zrób one-line template który aliasuje pole. Downstream tool myśli wtedy że gada z generic OIDC providerem bez wymogu wiedzy WolfieAuth-specific.

Approval gate

Jeśli org do której logujesz się ma requireMembershipApproval: true, pierwszy login ląduje claims.wolfieauth_membership_approval = "PENDING". Każdy template SDK middleware automatycznie odbija tych userów na ekran “waiting for approval” z buttonem logout. Dla hand-rolled flow sprawdź isApproved(claims):

import { isApproved } from '@wolfieauth/sdk-core/oidc/claims';

if (!isApproved(claims)) return redirect('/auth/pending');

Poza standardowy flow

Kilka endpointów istnieje których większość SDK-ów nie eksponuje, ale warto wiedzieć dla nietypowych scenariuszy:

  • Token introspection (POST /token/introspection, RFC 7662) — Twój backend może zapytać WolfieAuth “czy ten access token jest jeszcze ważny i co w nim siedzi?” Przydatne gdy upstream proxy widzi token ale nie ma JWKS scache-owanego.
  • Token revocation (POST /token/revocation, RFC 7009) — explicite unieważnij access lub refresh token przed wygaśnięciem (np. user kliknął “log out everywhere”). Cookie sesji na auth.wolfieguard.com jest revoke-owane osobno przez /session/end.
  • Userinfo endpoint (GET /me z Authorization: Bearer <access_token>) — zwraca ten sam claim set co id_token, scope-gated. Większość SDK-ów dotyka tego raz przy logowaniu żeby zapełnić session cookie; rzadko musisz wołać bezpośrednio.
  • Public branding endpoint (GET /api/public/clients/<twoj-client-id>/branding) — zwraca theme tokens (kolory, logo, copy strings) skonfigurowane dla Twojej apki. Konsumenci SDK używają tego do renderowania login button stylowanego pod issuer-a. Bez auth, safe wołać z przeglądarki.
  • Discovery (GET /.well-known/openid-configuration) i JWKS (GET /jwks) — dwa URL-e które każda OIDC client library auto-discoveruje. Jeśli integrujesz starszą bibliotekę która tego nie robi, te są URL-ami do wskazania.

Jeśli third-party tool który próbujesz integrować pyta o którykolwiek z URL-i powyżej, wszystkie siedzą pod https://auth.wolfieguard.com/.

Co jeśli WolfieAuth jest down?

Wtedy nikt nie może się zalogować. To realna konsekwencja używania hostowanego auth providera, i powinieneś to akceptować tak samo jak akceptujesz że Twoja apka idzie down gdy AWS pada. Mitygacje:

  • Cache JWKS aggressywnie — apka boot-uje nawet gdy WolfieAuth nieosiągalny, dopóki ma cached JWKS.
  • Refresh tokeny mają długie TTL (default 30d) — istniejący zalogowani userzy działają dalej.
  • Używaj claim wolfieauth_plans, nie roundtripów — jeśli gating czyta z JWT, Twoja apka nie martwi się czy WolfieAuth jest osiągalny per request.

Czytaj dalej

Ostatnia aktualizacja: