V8 Central — Product & Technical Spec
  1. Docs
  2. Core platform
  3. Authentication

#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

#FeatureRules (V8 doc)V7 reference
2.1RegisterName, 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 screensPOST /users (also default_plan, company_size, industry, management_experience, heard_about_us, cf_token)
2.2Email verificationAccount inactive until verified. Resend allowed. Link valid 24 hGET /verify/{token}, POST /resend/verification-link
2.3LoginEmail + 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.4Google / GitHubRedirect → 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 loginPOST /users/{provider}/url, GET /users/{provider}/callback (provider ∈ google, github)
2.5Forgot / resetReset 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 refusedPOST /forgot-password, POST /reset-password
2.6Invitation passwordInvited user sets a password from the invite linkPOST /user/password-set/{token}
2.72FA at loginOne 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 passesPOST /two-factor-authentication/verify (email, code, google_auth), …/resend
2.8IP whitelistUnknown 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 againGET /user/whitelist-ip/{key}/authorize
2.9TokensBearer 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 allWas Passport until 2026-10-08. The reuse rule is a hard frontend constraint — see API architecture
2.10LogoutRevokes the current tokenGET /user/logout
2.11SecurityBuilt 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 approvalthrottle:ratelimit

#Login flow

StateFrontend behaviour
IdleEmail + password fields. Submit disabled until both are valid (email format, password 8–64 chars as in V7)
LoadingButton spinner, fields read-only, double submit blocked
Validation error (422)Field messages from errors
Wrong credentialsOne generic message ("Incorrect email or password"). Never say which one is wrong
UnverifiedSpecific message + "Resend verification email"
Banned / lockedSpecific message, no retry
IP not whitelisted"We emailed an approval link for this IP" screen
2FA requiredCode 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 / 5xxInline error + Retry. No redirect
SuccessStore 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

  1. FE calls logout, which revokes the token on BE. V8 requirement 2.10
  2. FE clears the session cookie / token store, Zustand stores (current organization, cached lists) and any per-user cache.
  3. 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 /me and GET /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 in httpOnly cookies.
  • Server components and route handlers attach Authorization: Bearer when 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 /login or /register are 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

ItemState
Username loginNot supported Confirmed
Magic link / passkeysNot in any source
"Remember me"Not specified. Token lifetimes are fixed (15/30 days) V8 requirement
Session expiry UXRefresh once, then re-login. Exact UX Assumption
Confirmation timer (V7 confirmation_timer)"Ignored for now" V8 requirement Phase 3
ServerAvatar V8 Central · prepared by central-app-2 (Pair 2 frontend) for Bhavik Jethwa · the frontend in this spec is not built yet; the backend started on 2026-10-08 · Built 2026-10-29 03:20 UTC