Skip to content

Auth Flow

sequenceDiagram
    participant User
    participant SPA as Cloud SPA
    participant API as Cloud Backend
    participant DB as Azure SQL

    User->>SPA: Visit /login
    User->>SPA: Enter email
    SPA->>API: POST /api/v1/auth/check-method { email }
    API->>DB: SELECT auth_policy, sso_config WHERE tenant matches email domain
    DB-->>API: { method: "password" | "sso" }
    API-->>SPA: { method: "password" }

    User->>SPA: Enter password
    SPA->>API: POST /api/v1/auth/login { email, password }
    API->>DB: SELECT user by email
    API->>API: bcrypt.Compare(password, hash)
    API->>DB: SELECT tenant_licenses (check active)

    alt MFA enabled
        API-->>SPA: { mfa_required: true, session_token }
        User->>SPA: Enter TOTP code
        SPA->>API: POST /api/v1/auth/mfa/verify { totp_code, session_token }
        API->>API: Verify TOTP (pquerna/otp)
    end

    API->>API: Sign access JWT (60 min)
    API->>DB: INSERT refresh_tokens
    API-->>SPA: { access_token } + Set-Cookie: refresh_token (HttpOnly)
    SPA->>SPA: authStore.setAccessToken(access_token)
    SPA->>API: GET /api/v1/me
    API-->>SPA: { user, permissions, features }
    SPA->>SPA: authStore.setUser(...)
    SPA-->>User: Redirect to /dashboard
sequenceDiagram
    participant User
    participant SPA as Cloud SPA
    participant API as Cloud Backend
    participant Entra as Microsoft Entra ID
    participant DB as Azure SQL

    User->>SPA: Click "Sign in with Microsoft"
    SPA->>API: GET /api/v1/auth/microsoft/login
    API-->>User: 302 → Entra ID authorize URL
    User->>Entra: Authenticate (corporate credentials)
    Entra-->>API: GET /api/v1/auth/microsoft/callback?code=...
    API->>Entra: Token exchange (code → ID token)
    Entra-->>API: ID token { email, oid, name }
    API->>DB: Find user by email (or create)
    API->>DB: Check tenant_licenses
    API->>API: Issue JWT pair
    API-->>User: 302 → {APP_BASE_URL}/auth/callback?token=<access_token>
    User->>SPA: /auth/callback
    SPA->>SPA: Extract token from URL, store in authStore
    SPA->>API: GET /api/v1/me
    SPA-->>User: Redirect to /dashboard
sequenceDiagram
    participant User
    participant SPA as Cloud SPA (SessionRestore)
    participant API as Cloud Backend

    User->>SPA: F5 (full reload)
    SPA->>SPA: Zustand hydrates: user set, access_token null
    SPA->>SPA: Wait for hasHydrated()
    SPA->>API: POST /api/v1/auth/refresh (cookie sent automatically)
    alt Refresh token valid
        API->>API: Validate JWT, check DB blacklist
        API->>API: Issue new access JWT
        API-->>SPA: { access_token }
        SPA->>SPA: authStore.setAccessToken(new_token)
        SPA-->>User: Render protected page
    else Expired or missing
        API-->>SPA: 401
        SPA->>SPA: authStore.clearAuth()
        SPA-->>User: Redirect to /login
    end
sequenceDiagram
    participant User
    participant SPA as Cloud SPA
    participant API as Cloud Backend
    participant DB as Azure SQL

    User->>SPA: Click Logout
    SPA->>API: POST /api/v1/auth/logout
    API->>DB: DELETE or invalidate refresh_token
    API-->>SPA: 200 + Set-Cookie: refresh_token= (cleared)
    SPA->>SPA: authStore.clearAuth()
    SPA-->>User: Redirect to /login