Skip to content

Authentication Flow

Flow Diagram

graph TB
    subgraph Entry
        A["HTTP Request"] --> B["CustomSession MW\n(cookie namespace)"]
    end

    B --> C{Surface}
    C -->|"/admin/*"| D["admin guard\nAdmin model\nbcrypt only"]
    C -->|"web routes"| E["web guard\nUser model\nbcrypt + SHA256 fallback"]
    C -->|"unauthenticated"| F["EnsureGuestUser\nJWT cookie\nutaten_store_guest_user"]

    D --> D1["AdminLoginController\nAuth::guard('admin')->attempt()"]
    D1 -->|success| D2["redirect admin.dashboard"]
    D1 -->|fail| D3["back with errors"]

    E --> E1["WebLoginController\nMembershipManager::verifyPassword()"]
    E1 --> E2["PasswordService\nbcrypt Hash::check()"]
    E2 -->|fail| E3["SHA256 fallback\n3000 iterations\nfixed salt"]
    E3 -->|match| E4["live upgrade\nrehash to bcrypt\nCopyUserCart"]
    E2 -->|match| E5["authenticated()\nCopyUserCart\nredirect"]
    E4 --> E5

    E5 --> G["verified middleware\nEnsureEmailIsVerified"]
    G --> H["address_required\nchecks address->isComplete()"]
    H --> I["Protected routes\nmypage, order, lottery_winner"]

    F --> F1["JWT decode\nfirebase/php-jwt HS256\nsession('_guest_user')"]
    F1 --> F2["CartItemPolicy\n?User nullable\nguest cart access"]

Purpose

Provides three separate identity contexts: authenticated web users, authenticated admins, and guest users. Keeps admin and user sessions isolated at the cookie level to prevent cross-contamination under Octane's persistent-process model.

Trigger Points

  • POST /login — web user login
  • POST /admin/login — admin login
  • Any request to web routes — EnsureGuestUser issues/validates guest JWT
  • POST /register — registration + email verification trigger

Actors

  • User model (web guard, session-based)
  • Admin model (admin guard, session-based)
  • MembershipManager / PasswordService (credential verification)
  • CartManager (guest cart migration on login)

Lifecycle Steps

Web User Login

  1. POST /loginWeb\Membership\Auth\LoginController::login()
  2. LoginController::attemptLogin() queries User where email + status = active
  3. MembershipManager::verifyPassword()PasswordService:
  4. Try Hash::check() (bcrypt)
  5. On failure: SHA256 fallback with fixed salt 975061db6fe25eacee72ba2743fc9e7634d4, 3000 stretch iterations
  6. On SHA256 success: rehash with bcrypt and save (live upgrade)
  7. authenticated(): CartManager::copyUserCart() (guest cart → user cart)
  8. Redirect to session('last_product_url') or RouteServiceProvider::HOME

Admin Login

  1. POST /admin/loginAdmin\Auth\LoginController::login()
  2. Auth::guard('admin')->attempt() — bcrypt only, no legacy fallback
  3. On success: to_route('admin.dashboard'); sets auth.password_confirmed_at in session

Guest Identity

  1. Every request on user middleware group → EnsureGuestUser
  2. If no cookie: generate firebase/php-jwt HS256 token, store as utaten_store_guest_user cookie (HttpOnly, Secure, SameSite=Strict, 1-day TTL)
  3. Decoded guest ID stored in session('_guest_user')
  4. CartItemPolicy accepts ?User $user (nullable) — guest operations validated via session

Post-Login Guards

  • verified (custom EnsureEmailIsVerified): blocks if !hasVerifiedEmail() AND !isActive()
  • address_required: lazy-loads $user->address, redirects to address form if incomplete

Laravel Components Involved

Component Path
Web LoginController app/Http/Controllers/Web/Membership/Auth/LoginController.php
Admin LoginController app/Http/Controllers/Admin/Auth/LoginController.php
MembershipManager app/Modules/Membership/MembershipManager.php
PasswordService app/Modules/Membership/Providers/PasswordService.php
CustomSession app/Http/Middleware/CustomSession.php
EnsureGuestUser app/Http/Middleware/EnsureGuestUser.php
AuthServiceProvider app/Providers/AuthServiceProvider.php
Auth config config/auth.php
Middleware Kernel app/Http/Kernel.php

Data Mutations

  • On SHA256 login: users.password updated to bcrypt hash
  • On login: sessions table updated (Laravel session driver)
  • On guest: cookie set (no DB write)
  • On cart copy: cart_items.user_id populated from cart_items.guest_id

State Transitions

guest (JWT only) → authenticated (session, verified, address complete) → protected routes
SHA256 password → bcrypt password (one-time upgrade on successful login)

Failure Paths

Scenario Behaviour
Wrong password Back with validation errors, no account enumeration
Inactive user status != active query filter — login silently fails same as wrong password
JWT decode failure Silently swallowed (no logging) — new guest JWT issued
Email unverified Redirect to verification notice
Address incomplete Redirect to address form (address_required middleware)
Admin tries web login Different guard — no cross-contamination

Security Considerations

  • Legacy SHA256 path: fixed salt derived from config + plaintext password prefix. Risk: if config/auth.php is exposed, all legacy hashes are crackable without per-user salt. Upgrade path active but relies on user logging in.
  • Guest JWT secret: HS256 shared key — if APP_KEY or JWT secret leaks, guest IDs can be forged for cart manipulation.
  • JWT decode failures: silently ignored with no logging — failed attempts are invisible.
  • LotteryCartItemPolicy: requires authenticated User (not nullable) despite Cart having guest support — guest access to lottery cart will throw.
  • AddressRequired: lazy-loads $user->address on every authenticated route — extra query per request (N+1 if batched).
  • Sanctum installed but EnsureFrontendRequestsAreStateful is commented out — API routes unprotected by Sanctum middleware.

Performance Considerations

  • SHA256 fallback: 3000 stretch iterations add ~5–10ms per legacy login
  • AddressRequired middleware: one extra SELECT on every authenticated web request
  • CartManager::copyUserCart() on login: batch update query, generally fast

Extension Points

  • PasswordService is bound via interface — swap legacy hash logic without touching controllers
  • EnsureGuestUser middleware is applied per-group — can be removed from specific route groups