#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. No onboarding questions — removed by the backend on 2026-10-09 (Vishal) for sign-up. ⚠️ The questions still live in the profile endpoint (3.2), which Bhavik's rule also bans — so the disagreement is closed for this screen only; see FE: Account screens | 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. Added 2026-10-09 (Vishal): linking or creating needs a provider-verified email; new social users are verified at once — no activation email (unlike V7); an unverified account becomes verified on social login | 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. Added 2026-10-09 (Vishal): a successful reset also marks the email as verified, and banned accounts cannot reset — no email is sent and an older link is refused | 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 | One method per user — email code or Google Authenticator, never both (as V7, confirmed 2026-10-09), plus one-time backup codes (stored hashed). Email code: 10 min, max 5 wrong tries, resend. Authenticator: ±30 s drift tolerated, a code cannot be reused. 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. Added 2026-10-09 (Vishal): exact IP matching, as V7 — no ranges (ranges only ever as manual entries on the whitelist page, "if ever"); a new approval email at most once every 5 minutes per IP; after approving, the user logs in again | GET /user/whitelist-ip/{key}/authorize |
| 2.9 | Tokens | Bearer 15 days + refresh 30 days. Built 2026-10-09: a Sanctum personal access token (15 d) plus Central's own refresh token (30 d) — stored hashed, never usable as a Bearer token, rotated on every refresh, and reusing a spent refresh token revokes the whole login chain. Logout revokes the current pair; a password reset revokes all | Was Passport until 2026-10-08. The reuse rule is a hard frontend constraint — see API architecture |
| 2.10 | Logout | Revokes the current token | GET /user/logout |
| 2.11 | Security | Built 2026-10-09: rate limits on every auth endpoint with a translated 429; an auth.active group on protected routes — a banned account is refused and logged out everywhere, an unverified one refused. Permission middleware waits for Phase 4 roles. 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 /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. Decided for V8 on 2026-10-09 (Bhavik): no trial, and no plan is chosen inside the sign-up form. The user signs up, then lands on a Choose a plan step at/billing/plan— so registration itself carries no plan field. See FE: Plans & subscription. - 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
/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
/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 (2026-10-08): Sanctum Bearer tokens only, plus a refresh token Central builds itself.
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.
The alternatives are now down to one bad option. Sanctum SPA cookies was the obvious competitor and the backend has ruled it out itself (1.4, 2026-10-08). A token in memory still conflicts with the cookie rule and loses the session on every reload. So the BFF is not merely preferred — it is what is left. Open question Decision D-1 in Open questions is now only about the response shape and where Next.js keeps the tokens.
#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 |