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)
Related Documentation¶
- Security Architecture - Deep dive on JWT, CSRF, encryption
- RBAC Design - Permission system details
- Auth Flows - Sequence diagrams for all flows
- API Reference - Endpoint documentation