#Authentication
V8 Phase 2 (Authentication & Security) is "requirements complete" but not built V8 requirement Planned, not built. sa-central-api-2 has no auth endpoints yet Missing. Endpoint paths below are V7 references, not V8 contracts. V8 paths are decided when Phase 2 is planned.
#Feature list
| # | Feature | Rules (V8 doc) | V7 reference |
|---|---|---|---|
| 2.1 | Register | Name, email, password (min 8). Cloudflare Turnstile. Optional referral / affiliate / invitation code + onboarding questions | POST /users (also default_plan, company_size, industry, management_experience, heard_about_us, cf_token) |
| 2.2 | Email verification | Account inactive until verified. Resend allowed. Link valid 24 h | GET /verify/{token}, POST /resend/verification-link |
| 2.3 | Login | Email + password. Separate messages for unverified and banned. Wrong credentials → one generic message. Success → token + user + organizations. Recorded in login history (IP, browser) | POST /login → access_token, token_type, expires_in, user, organizations |
| 2.4 | Google / GitHub | Redirect → callback. New users registered. An existing email is linked automatically. Same 2FA + IP checks | POST /users/{provider}/url, GET /users/{provider}/callback (provider ∈ google, github) |
| 2.5 | Forgot / reset | Reset email. Link valid 60 min. Reset logs out all devices | POST /forgot-password, POST /reset-password |
| 2.6 | Invitation password | Invited user sets a password from the invite link | POST /user/password-set/{token} |
| 2.7 | 2FA at login | Email code (10 min, max 5 wrong tries, resend), Google Authenticator, or backup code. Token only after 2FA passes | POST /two-factor-authentication/verify (email, code, google_auth), …/resend |
| 2.8 | IP whitelist | Unknown IP blocked, approval link emailed (24 h). Approved IP allowed afterwards | GET /user/whitelist-ip/{key}/authorize |
| 2.9 | Tokens | Bearer 15 days + refresh 30 days. Refresh gives a new token and revokes the old | Passport in V7 too |
| 2.10 | Logout | Revokes the current token | GET /user/logout |
| 2.11 | Security | Rate limits on register, login, 2FA, resends, forgot password, IP approval | throttle:ratelimit |
#Login flow
| State | Frontend behaviour |
|---|---|
| Idle | Email + password fields. Submit disabled until both are valid (email format, password 8–64 chars as in V7) |
| Loading | Button spinner, fields read-only, double submit blocked |
| Validation error (422) | Field messages from errors |
| Wrong credentials | One generic message ("Incorrect email or password"). Never say which one is wrong |
| Unverified | Specific message + "Resend verification email" |
| Banned / locked | Specific message, no retry |
| IP not whitelisted | "We emailed an approval link for this IP" screen |
| 2FA required | Code screen. Method switch (email code / authenticator / backup code), resend with cooldown, attempts left |
| Rate limited (429) | "Too many attempts, try again in N s" from Retry-After |
| Network / 5xx | Inline error + Retry. No redirect |
| Success | Store the session (see below), set current organization, go to ?next= (same-origin path only) or /[locale]/dashboard |
V7 reference: login response shapes (not a V8 contract)
// 2FA off — POST /login
{"access_token": "eyJ…", "token_type": "Bearer", "expires_in": 1767225600,
"user": {…}, "organizations": [{…}]}
// 2FA on — token withheld until POST /two-factor-authentication/verify
{"access_token": null, "token_type": null, "expires_in": null,
"user": {…}, "organizations": null}#Registration flow
- An organization is not created at registration. V8 requirement
- An initial plan at registration: V7 accepts
default_plan(Pro / Standard) and creates Free/trial plans. V8 hasn't decided Open question. - An invitation code in the URL pre-fills and links the pending membership. V7 only
invitation_token - Dropped from V7: throwaway-email block, Gmail-dot check, device fingerprint. V8 requirement
#OAuth (Google / GitHub)
- Keys are managed in the admin panel, and read from the database until it exists. V8 requirement
- Failure cases: provider disabled ("configuration is disabled" in V7), consent cancelled, email missing from provider, state mismatch. Each returns to
/[locale]/login?error=<code>with a translated message. Assumption error codes still to define.
#Logout
- FE calls logout, which revokes the token on BE. V8 requirement 2.10
- FE clears the session cookie / token store, Zustand stores (current organization, cached lists) and any per-user cache.
- Redirect to
/[locale]/login. Logout links are not prefetched.
#Current user / session restore
- On every protected page load, the app needs current user + organizations to render the shell. V7 has
GET /meandGET /auth/user/organizations. V8 needs the same Missing. - If the token is expired, use the refresh token once (V8 2.9). If that fails, clear the session and go to login with
?next=.
#Session handling (frontend)
Required by project rules: "cookie-based session for SSR", tokens never in client bundles, cookies httpOnly, secure, sameSite=Lax. Required by V8 doc: Passport bearer + refresh tokens.
Recommended (needs approval): a BFF pattern inside Next.js.
- Next.js route handlers (
/api/auth/*) call sa-central-api-2 login/refresh/logout and keep the access and refresh tokens inhttpOnlycookies. - Server components and route handlers attach
Authorization: Bearerwhen calling the API. The browser never reads the token.
Alternatives (Sanctum SPA cookies, or a token in memory) conflict with the Passport decision or the cookie rule. Open question Decision D-1 in Open questions.
#Protected routes
- Public: login, register, verify, forgot/reset, invitation accept, IP-approval result, OAuth callback.
- Logged-in users who open
/loginor/registerare sent to the dashboard.
#Account features (Phase 3, reached from the user menu)
Profile, change email (confirm link to new address + notice to old), change password (logs out other devices), email preferences, delete protection, delete account (password; negative balance must be paid first), 2FA + backup codes (stored encrypted), Google Authenticator (on only after one correct code), IP whitelist (turning it on adds the current IP), login history, account activity, API access on/off, active sessions with log out one/all (new in V8). V8 requirement Phase 3
#Not supported / not decided
| Item | State |
|---|---|
| Username login | Not supported Confirmed |
| Magic link / passkeys | Not in any source |
| "Remember me" | Not specified. Token lifetimes are fixed (15/30 days) V8 requirement |
| Session expiry UX | Refresh once, then re-login. Exact UX Assumption |
Confirmation timer (V7 confirmation_timer) | "Ignored for now" V8 requirement Phase 3 |