Skip to main content

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​

Web Console Sign-In Flow
  1. Credentials Entry: Operator enters email and password on the login screen.
  2. 2FA Verification (if enabled): Enters the time-based one-time authentication code.
  3. Session Cookie: Issues a secure, encrypted HTTP session cookie (HttpOnly, SameSite=Strict).
  4. 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:

  1. 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 to ACTIVE.
    • Backward compatibility: legacy 64-character SHA-256 password hashes are transparently upgraded to BCrypt upon successful sign-in.
  2. Email OTP 2FA Verification:
    • Unless the organization's MFA policy is set to DISABLED, the backend returns requireOtp: true along with a preAuthTicket (Base64 payload containing email, 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-otp once the cooldownSec countdown reaches 0.
    • In development mode only (development mode and ota.security.otp.dev-bypass=true), a helper banner displays the devMockCode (123456). This bypass is strictly blocked in production by OtpHardProductionGuard.

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, and organizationName. The backend checks that no ACTIVE user already exists with that email (otherwise returning HTTP 409 Conflict), generates a REGISTER_VERIFY OTP, and sends the verification email.
  • Step 2 (OTP Verification and Provisioning): The operator submits action: "verify" along with the OTP code. The backend consumes the OTP, creates the TenantJpaEntity (with a slugified lowercase identifier and enforceFourEyes = false by default in Solo mode), and creates the ACTIVE administrator account with ROLE_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 returns HTTP 200 OK with the generic message "Si un compte correspondant existe, un code de réinitialisation a été envoyé.". When a matching active account exists, a PASSWORD_RESET OTP is generated and emailed.
  • Step 2 (POST /api/auth/password-reset/confirm): Accepts { email, code, password } (minimum 8 characters). Verifies and consumes the PASSWORD_RESET OTP, 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 containing username, email, fullName, role, tenantId, organizationName, token, and loggedInAt.
  • 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 NameValueUsage in middleware.ts
tks_sessionSession token (or "authenticated")Confirms that the operator has an active session
tks_tenantTenant identifier (e.g., sara-money)Injected into the x-tenant-id HTTP header
tks_roleRBAC 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:

  1. Activity Monitoring: Listens to DOM mousemove, mousedown, keydown, scroll, and touchstart events.
  2. 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).
  3. 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 sets tks_session_locked = "true" in localStorage (preventing bypass via page refresh).
  4. BCrypt Password Unlock:
    • The operator must enter their password to unlock the session.
    • The component calls POST /api/admin/v1/auth/unlock (falling back to POST /api/auth/login), which verifies on the server that the user account is still ACTIVE and 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_session cookie is redirected to /login?redirect=<pathname> (or receives HTTP 401 on /api/* routes).
  • Authenticated Redirect: Signed-in operators visiting /login, /register, or /forgot-password are 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: DENY
    • X-Content-Type-Options: nosniff
    • Referrer-Policy: strict-origin-when-cross-origin
    • Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
    • Strict-Transport-Security: max-age=31536000; includeSubDomains