Authentication, Email OTP 2FA, and Session
The TkushPatch Web Console implements a multi-factor authentication pipeline combining BCrypt (cost 12) password verification, 6-digit email OTP two-factor authentication (2FA), tenant-scoped session cookies, and an automatic inactivity lock screen.
Authentication Flow
- Credentials Entry: Operator enters email and password on the login screen.
- 2FA Verification (if enabled): Enters the time-based one-time authentication code.
- Session Cookie: Issues a secure, encrypted HTTP session cookie (
HttpOnly,SameSite=Strict). - Dashboard Access: Redirects to the active application fleet overview.
1. Login Page (/login)
The /login page (src/app/login/page.tsx) operates in two steps:
- Credentials Entry:
- Work Email (
email) and Password (password). - Advanced option: operators belonging to multiple workspaces can specify a target organization identifier (
requestedTenantId). - First-time sign-in for invited collaborators (
status = INVITED,passwordHash = null): the password entered on first sign-in is automatically hashed with BCrypt (BCryptPasswordEncoder(12)) and the account status transitions toACTIVE. - Backward compatibility: legacy 64-character SHA-256 password hashes are transparently upgraded to BCrypt upon successful sign-in.
- Work Email (
- Email OTP 2FA Verification:
- Unless the organization's MFA policy is set to
DISABLED, the backend returnsrequireOtp: truealong with apreAuthTicket(Base64 payload containingemail,tenantId,role,fullName), expiration duration (expiresInSec), and resend cooldown (cooldownSec). - The operator enters the 6-digit code sent via email (6-slot input with automatic paste support).
- A Resend code button calls
POST /api/auth/resend-otponce thecooldownSeccountdown reaches0. - In development mode only (
development modeandota.security.otp.dev-bypass=true), a helper banner displays thedevMockCode(123456). This bypass is strictly blocked in production byOtpHardProductionGuard.
- Unless the organization's MFA policy is set to
2. Organization Registration (/register)
The /register page (src/app/register/page.tsx) provisions a new organization (tenant) and its initial administrator (ROLE_ADMIN) in two steps via POST /api/auth/register:
- Step 1 (Initial Request and OTP Dispatch): The operator submits
fullName,email,password, andorganizationName. The backend checks that noACTIVEuser already exists with that email (otherwise returningHTTP 409 Conflict), generates aREGISTER_VERIFYOTP, and sends the verification email. - Step 2 (OTP Verification and Provisioning): The operator submits
action: "verify"along with the OTPcode. The backend consumes the OTP, creates theTenantJpaEntity(with a slugified lowercase identifier andenforceFourEyes = falseby default in Solo mode), and creates theACTIVEadministrator account withROLE_ADMIN.
3. Forgot Password (/forgot-password)
The /forgot-password page (src/app/forgot-password/page.tsx) resets account passwords while preventing account enumeration:
- Step 1 (
POST /api/auth/password-reset/request): Accepts{ email }. To prevent account enumeration, the endpoint always returnsHTTP 200 OKwith the generic message"Si un compte correspondant existe, un code de réinitialisation a été envoyé.". When a matching active account exists, aPASSWORD_RESETOTP is generated and emailed. - Step 2 (
POST /api/auth/password-reset/confirm): Accepts{ email, code, password }(minimum 8 characters). Verifies and consumes thePASSWORD_RESETOTP, then updates the BCrypt password hash across all matching active accounts.
4. Session Management and Cookies (AuthContext)
Once authenticated, AuthProvider (src/lib/auth/AuthContext.tsx) persists the operator session:
Client Storage (localStorage)
tks_user_session: JSON object containingusername,email,fullName,role,tenantId,organizationName,token, andloggedInAt.tks_session_locked: Boolean string ("true") set when the session is locked due to inactivity.
HTTP Cookies (Read by Next.js Middleware)
Three cookies are written with path=/; max-age=86400; SameSite=Lax (24-hour lifetime):
| Cookie Name | Value | Usage in middleware.ts |
|---|---|---|
tks_session | Session token (or "authenticated") | Confirms that the operator has an active session |
tks_tenant | Tenant identifier (e.g., sara-money) | Injected into the x-tenant-id HTTP header |
tks_role | RBAC role (e.g., ROLE_SECURITY_OFFICER) | Evaluated by hasAccessToRoute(userRole, pathname) and injected into x-user-role |
On sign-out (logout()), tks_user_session and tks_session_locked are removed from localStorage, all three cookies are expired (max-age=0), and the browser redirects to /login.
5. Automatic Inactivity Lock (IdleTimer)
The IdleTimer component (src/lib/auth/IdleTimer.tsx) protects unattended operator workstations:
- Activity Monitoring: Listens to DOM
mousemove,mousedown,keydown,scroll, andtouchstartevents. - Inactivity Timeout (
IDLE_TIMEOUT_MS): After 15 minutes (15 * 60 * 1000 ms) of inactivity, a warning banner appears with a 60-second countdown (WARNING_SECONDS = 60). - Session Lock (
isLocked = true): If the operator does not click Keep session active before the countdown expires, a full-screen lock overlay blocks the UI and setstks_session_locked = "true"inlocalStorage(preventing bypass via page refresh). - BCrypt Password Unlock:
- The operator must enter their password to unlock the session.
- The component calls
POST /api/admin/v1/auth/unlock(falling back toPOST /api/auth/login), which verifies on the server that the user account is stillACTIVEand matches the stored BCrypt hash (verifyPassword). - Upon verification, the lock overlay is dismissed while preserving the current page state.
6. Next.js Middleware (src/middleware.ts)
src/middleware.ts intercepts all incoming requests (excluding _next/static, _next/image, favicon.ico, and static images):
- Exempt Public Routes:
/login,/register,/forgot-password,/cli/login,/api/auth/*,/api/cli/token/*,/api/admin/*,/api/patches/*,/api/devices/*,/api/network-info/*. - Unauthenticated Redirect: Any request to a protected page without a
tks_sessioncookie is redirected to/login?redirect=<pathname>(or receivesHTTP 401on/api/*routes). - Authenticated Redirect: Signed-in operators visiting
/login,/register, or/forgot-passwordare automatically redirected to/. - Page-Level RBAC Enforcement: If
!hasAccessToRoute(userRole, pathname), the middleware redirects to/?forbidden=true&attempted=<pathname>&role=<userRole>. - Injected Security Headers:
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=()Strict-Transport-Security: max-age=31536000; includeSubDomains