Skip to content

Authentication System Architecture

Overview

MBPanel implements an enterprise-grade multi-tenant authentication and authorization system with:

  • JWT-based stateless tokens (access) + Redis-backed session state (refresh)
  • Two-step login flow (credentials → team selection → tokens)
  • Role-Based Access Control (RBAC) with wildcard permissions
  • Advanced security: Device approval, suspicious login detection, concurrent login control
  • Virtuozzo integration: Owner-only session key management

High-Level Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                              CLIENT (Browser)                               │
│                    Next.js 16 + React 19 + Server Actions                  │
└─────────────────────────────────────────────┬───────────────────────────────┘
                                              │ HTTPS + HttpOnly Cookies
┌─────────────────────────────────────────────────────────────────────────────┐
│                          FASTAPI BACKEND                                    │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │                      Middleware Chain (reverse order)                │  │
│  │  CorrelationId → CORS → OTEL → AuthContext → RequestLogging         │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │                         Auth Module                                  │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌────────────┐  │  │
│  │  │   Router    │→ │   Service   │→ │ Repository  │→ │  Database  │  │  │
│  │  └─────────────┘  └─────────────┘  └─────────────┘  └────────────┘  │  │
│  │         ↓                ↓                ↑                  ↑        │  │
│  │    Schemas         JWT/Security      Redis ←───────────┘           │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │                      External Services                                │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐              │  │
│  │  │   Virtuozzo  │  │  Postmark    │  │  Geo-IP API  │              │  │
│  │  └──────────────┘  └──────────────┘  └──────────────┘              │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────────┘

Core Data Model

Primary Entities

User (global identity)
├── id, email, hashed_password
├── is_verified, is_active, is_superuser
└── belongs_to → TeamMember(s)

Team (tenant boundary)
├── id, team_name, slug, status
├── owner_user_id (foreign key → User)
├── Virtuozzo credentials (encrypted)
└── has_many → TeamMember(s)

TeamMember (user-team association)
├── user_id, team_id (unique constraint)
├── role_id (foreign key → Role)
└── belongs_to → Team, User, Role

Role (team-scoped)
├── id, team_id, name, is_editable
└── has_many → TeamMember(s), RolePermission(s)

Permission (global, seeded)
├── slug (primary key, e.g., "team.manage")
└── has_many → RolePermission(s)

RolePermission (many-to-many)
├── role_id, permission_slug
└── links Role ↔ Permission

Security & Session Tables

Table Purpose
ActiveSession Single active refresh session per user
LoginActivity Audit log of login/refresh events
TrustedDevice Persistent device trust records
InviteToken Single-use team invitations
EmailVerificationToken Email verification (24h TTL)
PasswordResetToken Password reset (1h TTL)

Token Structure

Access Token (6 hour TTL)

Claims:

{
  "sub": "123",           // user_id (subject)
  "type": "access",       // token type
  "user_name": "John Doe",
  "team_id": 456,
  "team_name": "Acme Corp",
  "role_id": 1,
  "role_name": "Owner",
  "permissions": ["*"],   // or ["site.create", "env.deploy", ...]
  "jti": "abc123...",     // unique token ID
  "iat": 1234567890,      // issued at (unix timestamp)
  "exp": 1234585890       // expiration (unix timestamp)
}

Storage: HttpOnly cookie (mbpanel_access)

Validation: 1. Signature verification (HS256) 2. Expiration check 3. Blacklist check (Redis) 4. Revocation timestamp check (Redis)

Refresh Token (7 day / 1 day TTL)

Claims: Same structure as access token, type: "refresh"

Storage: HttpOnly cookie (mbpanel_refresh) + Redis session state

Redis Session State:

{
  "user_id": 123,
  "team_id": 456,
  "role_name": "Owner",
  "permissions": ["*"],
  "session_info": {
    "device_fingerprint": "...",
    "ip": "1.2.3.4",
    "user_agent": "...",
    "geo": {"city": "SF", "country": "US"}
  },
  "remember_me": true,
  "last_seen_at": "2025-01-22T10:30:00Z"
}

Two-Step Login Flow

┌──────────────┐                  ┌──────────────┐                  ┌──────────────┐
│   CLIENT     │                  │   BACKEND    │                  │    REDIS     │
└──────┬───────┘                  └──────┬───────┘                  └──────┬───────┘
       │                                 │                                 │
       │  POST /auth/login               │                                 │
       │  {email, password, remember_me} │                                 │
       ├─────────────────────────────────>│                                 │
       │                                 │ verify_password()                │
       │                                 │ check is_verified               │
       │                                 │ generate pre_auth_token          │
       │                                 ├─────────────────────────────────>│
       │                                 │ SET preauth:{token} = user_id    │
       │                                 │                                 │
       │  {pre_auth_token, teams: [...]} │                                 │
       │<─────────────────────────────────┤                                 │
       │                                 │                                 │
       │  User selects team              │                                 │
       │                                 │                                 │
       │  POST /auth/session-exchange    │                                 │
       │  {pre_auth_token, team_id, ...} │                                 │
       ├─────────────────────────────────>│                                 │
       │                                 │ GET preauth:{token}              │
       │                                 ├─────────────────────────────────>│
       │                                 │ check TeamMember                 │
       │                                 │ check Team.status                │
       │                                 │ load Role + Permissions          │
       │                                 │ check ActiveSession (concurrent) │
       │                                 │ create_access_token()            │
       │                                 │ create_refresh_token()           │
       │                                 │ SET refresh:{jti}                │
       │                                 ├─────────────────────────────────>│
       │                                 │ check LoginActivity (suspicious) │
       │                                 │                                 │
       │  Set-Cookie: access_token       │                                 │
       │  Set-Cookie: refresh_token      │                                 │
       │  Set-Cookie: csrf_token         │                                 │
       │<─────────────────────────────────┤                                 │
       │                                 │                                 │

Security Features

Feature Implementation
Password Hashing bcrypt with configurable rounds (default 12)
JWT Algorithm HS256 with 32+ char secret
CSRF Protection Double-submit cookie pattern
Rate Limiting Redis INCR counter (login endpoint)
Token Blacklist Redis key on logout/revocation
Device Approval Email OTP with 5-min TTL
Suspicious Login Geo-IP lookup + approve/deny flow
Session Timeout Idle (30-60m) + absolute (7d/1d)
Concurrent Login Single-session enforcement
Encryption at Rest Fernet for Virtuozzo creds

Virtuozzo Integration

Owner-Only Session Key Management:

Team VZ Session Key Lifecycle:
┌─────────────────────────────────────────────────────────────┐
│ 1. Lazy Refresh: Only when expired/near-expiry              │
│ 2. Per-Team Lock: Redis lock prevents stampede              │
│ 3. Owner-Only: Non-owners get 403 if refresh needed         │
│ 4. Cache-First: Redis cache before DB lookup                │
│ 5. Safety Window: Refresh 5-10 min before actual expiry     │
└─────────────────────────────────────────────────────────────┘

Storage: - Encrypted session key on Team.session_key_encrypted - Metadata: session_key_expires_at, session_key_last_refreshed - Redis cache: vz:session:{team_id} (short TTL)