Skip to content

Authentication System - Developer Quick Start

Prerequisites

  • Python 3.12+
  • Docker and Docker Compose
  • Access to the codebase

Local Development Setup

1. Environment Variables

Copy the example env file and configure required settings:

cp env/backend-local.example backend/.env.backend.local

Required auth-related variables:

# JWT Configuration
JWT_SECRET_KEY=your-secret-key-min-32-chars!!!!
JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=360
REFRESH_TOKEN_EXPIRE_MINUTES=10080
REFRESH_TOKEN_EXPIRE_MINUTES_SHORT=1440

# Password Hashing
BCRYPT_ROUNDS=12

# Redis (session state, rate limiting, blacklists)
REDIS_URL=redis://redis:6379/0

# Encryption (Virtuozzo creds, TOTP secrets)
ENCRYPTION_KEY=base64-urlsafe-32-byte-key

# Database
DATABASE_URL=postgresql+asyncpg://user:pass@postgres:5432/mbpanel  # pragma: allowlist secret (example only)

# Email (Postmark - for OTP, verification, password reset)
POSTMARK_SERVER_TOKEN=your-postmark-token
POSTMARK_FROM_EMAIL=noreply@yourdomain.com
POSTMARK_DEVICE_APPROVAL_TEMPLATE_ID=123456
POSTMARK_SUSPICIOUS_LOGIN_TEMPLATE_ID=234567

2. Start Infrastructure

make up
# Or: docker-compose up -d postgres redis rabbitmq

3. Install Backend Dependencies

cd backend
pip install --no-deps -c ../constraints.txt -e .

4. Run Migrations

make migrate
# Or: alembic upgrade head

5. Start Development Server

make dev-backend
# Or: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

The API will be available at http://localhost:8000

6. Access API Documentation

  • Swagger UI: http://localhost:8000/docs (requires basic auth if configured)
  • ReDoc: http://localhost:8000/redoc

Common Development Tasks

Running Tests

# All backend tests
make test-backend

# Unit tests only (no infrastructure required)
pytest tests/unit/ -v

# Integration tests (requires Redis, Postgres)
pytest tests/integration/ -v

# Auth module tests specifically
pytest tests/unit/modules/auth/ -v

# With coverage
pytest tests/unit/modules/auth/ --cov=app/modules/auth --cov-report=html

Seeding Test Data

make seed
# Creates demo users, teams, roles, permissions

Debugging Auth Issues

Enable debug logging:

# In backend/app/core/config.py
LOG_LEVEL = "DEBUG"

Check Redis state:

docker-compose exec redis redis-cli
> KEYS preauth:*
> GET preauth:some-token
> KEYS refresh:*
> GET blacklist:some-jti

View active sessions:

docker-compose exec db psql -U mbpanel -d mbpanel
> SELECT * FROM active_sessions WHERE revoked_at IS NULL;

Adding a New Auth Endpoint

  1. Define schemas in backend/app/modules/auth/schemas.py:
from pydantic import BaseModel

class MyAuthRequest(BaseModel):
    some_field: str

class MyAuthResponse(BaseModel):
    result: str
  1. Add business logic in backend/app/modules/auth/service.py:
class AuthService:
    async def my_auth_method(self, payload: MyAuthRequest) -> MyAuthResponse:
        # Your logic here
        return MyAuthResponse(result="success")
  1. Add route in backend/app/modules/auth/router.py:
@router.post("/my-endpoint", response_model=MyAuthResponse)
async def my_endpoint(
    payload: MyAuthRequest,
    user: UserDep,  # Requires authentication
    service: AuthService = Depends(get_auth_service_dep),
):
    return await service.my_auth_method(payload)

Adding a New Permission

  1. Seed the permission in a migration or seed script:
# In backend/app/infrastructure/database/models/rbac.py
PERMISSIONS = [
    # ... existing permissions
    Permission(slug="my.new.permission", description="..."),
]
  1. Run migration/seed

  2. Assign to roles via the API or admin interface

  3. Protect endpoints using the permission:

from app.core.dependencies import require_permission

@router.post("/protected")
async def protected_endpoint(
    _: None = Depends(require_permission("my.new.permission")),
    # ...
):
    # Only users with this permission can access
    pass

Testing with Authentication

Using test user:

import pytest
from httpx import AsyncClient

@pytest.mark.asyncio
async def test_protected_endpoint(async_client: AsyncClient):
    # First, login to get tokens
    response = await async_client.post("/api/v1/auth/login", json={
        "email": "test@example.com",
        "password": "testpassword123",  # pragma: allowlist secret
    })
    pre_auth_token = response.json()["pre_auth_token"]

    # Exchange session
    response = await async_client.post("/api/v1/auth/session-exchange", json={
        "pre_auth_token": pre_auth_token,
        "team_id": 1,
    })

    # Extract cookies
    cookies = response.cookies

    # Make authenticated request
    response = await async_client.get(
        "/api/v1/auth/me",
        cookies=cookies
    )
    assert response.status_code == 200

File Locations

Component Location
JWT Token Logic backend/app/core/jwt.py
Password Hashing backend/app/core/security.py
Middleware backend/app/core/middleware.py
Dependencies backend/app/core/dependencies.py
Permissions backend/app/core/permissions.py
Auth Service backend/app/modules/auth/service.py
Auth Router backend/app/modules/auth/router.py
Auth Schemas backend/app/modules/auth/schemas.py
Auth Constants backend/app/modules/auth/constants.py
Database Models backend/app/infrastructure/database/models/

Useful Commands

# Check Redis for active tokens
docker-compose exec redis redis-cli KEYS "refresh:*"

# Check active sessions in database
docker-compose exec db psql -U mbpanel -d mbpanel -c "SELECT * FROM active_sessions;"

# View recent login activity
docker-compose exec db psql -U mbpanel -d mbpanel -c "SELECT * FROM login_activity ORDER BY created_at DESC LIMIT 10;"

# Manually blacklist a token
docker-compose exec redis redis-cli SETEX "blacklist:token-jti" 604800 "1"

# Clear all Redis data (dev only!)
docker-compose exec redis redis-cli FLUSHALL

Troubleshooting

Issue Solution
Token validation fails Check JWT_SECRET_KEY matches between services
Redis connection errors Verify REDIS_URL and that Redis is running
Permission denied Check user has required permission via /api/v1/auth/me
Session expired Refresh tokens have TTL; user must re-login
Tests fail with Redis errors Ensure Redis is running: make up

Next Steps