Auth Service
The Auth Service handles authentication for AEGIS. It verifies email/password credentials, issues JWT tokens, and validates existing tokens for the gateway.
Overview
AEGIS uses a two-step authentication flow:
- The client authenticates with an email and password via the auth service, receiving a JWT token
- All subsequent requests include the JWT token in the
Authorization: Bearerheader (or theaegis_tokencookie) - The API gateway validates the JWT locally (using the shared secret) and extracts user identity
Users are stored in the PostgreSQL users table, and passwords are hashed with bcrypt. Accounts are admin-provisioned — there is no self-serve signup. In production the JWT secret would come from HashiCorp Vault.
Port & Language
| Property | Value |
|---|---|
| Port | 8009 |
| Language | Python 3.12 |
| Framework | FastAPI |
| Entry point | src/auth_service/main.py |
Key Endpoints
| Method | Path | Description |
|---|---|---|
POST | /auth/token | Exchange email + password for a JWT token. |
POST | /auth/validate | Validate a JWT token. Returns user_id and roles if valid. |
* | /auth/forward-auth | Method-agnostic check of the aegis_token cookie or Bearer header (used by Caddy). |
GET | /health | Health check. |
Architecture
src/auth_service/
├── __init__.py
├── main.py # Endpoints, JWT logic, password verification
└── create_user.py # CLI for provisioning/rotating usersAuthentication Flow
1. Client sends POST /auth/token with { "email": "...", "password": "..." }
2. Auth service looks up the user by LOWER(email)
3. Verifies the password against the stored bcrypt hash
4. If valid and active, generates a JWT with user_id, email, roles, and 24-hour expiry
5. Returns the token, expiry, and user info
6. Client stores the token and uses it for all subsequent requestsInvalid credentials or an inactive user return 401 Unauthorized with {"detail": "Invalid email or password"}. Email matching is case-insensitive.
JWT Token Structure
The JWT payload contains:
{
"user_id": "dev-user",
"email": "admin@aegis.local",
"roles": ["admin", "operator", "reviewer"],
"iss": "aegis-auth",
"iat": 1712793600,
"exp": 1712880000
}| Field | Description |
|---|---|
user_id | The authenticated user’s identifier |
email | The authenticated user’s email address |
roles | Array of role strings (used for RBAC in the gateway) |
iss | Issuer (aegis-auth) |
iat | Issued-at timestamp |
exp | Expiration timestamp (24 hours after issuance) |
Tokens are signed with HS256 using the JWT_SECRET environment variable.
Users Table
Users live in the PostgreSQL users table:
| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key, uuid_generate_v4() |
email | VARCHAR(255) | Not null; case-insensitive unique via idx_users_email_lower |
password_hash | TEXT | Not null; bcrypt hash |
roles | TEXT[] | Not null, default ARRAY['operator'] |
display_name | VARCHAR(200) | Optional |
is_active | BOOLEAN | Not null, default TRUE |
created_at / updated_at | timestamps |
The table is created in infrastructure/docker/postgres/init.sql for fresh databases, with a standalone idempotent migration at infrastructure/deploy/migrations/001_users.sql for existing databases.
Provisioning Users
There is no signup endpoint. Create users with the CLI:
cd services/auth-service
poetry run python -m auth_service.create_user \
--email alice@example.com \
--roles admin,operator,reviewerThe command prompts for a password, or reads it from AEGIS_NEW_USER_PASSWORD. Re-running it for an existing email rotates that user’s password.
Bootstrap Admin
On startup the service seeds an initial admin from BOOTSTRAP_ADMIN_EMAIL and BOOTSTRAP_ADMIN_PASSWORD if a user with that email does not already exist. For local development these default to admin@aegis.local / aegis-dev-admin.
| Password | Roles | |
|---|---|---|
admin@aegis.local | aegis-dev-admin | admin, operator, reviewer |
The default bootstrap credentials must not be used in any deployed environment. Set strong BOOTSTRAP_ADMIN_* values via environment variables.
Token Validation
The /auth/validate endpoint decodes and verifies a JWT token:
- Strip the
Bearerprefix if present - Decode using PyJWT with the
HS256algorithm and the sharedJWT_SECRET - Return
valid: truewith the user’s identity, orvalid: falseif the token is invalid or expired
This endpoint is used internally by the API gateway, though the gateway also validates tokens locally using its own Go JWT library. The /auth/forward-auth endpoint performs the same check but is method-agnostic and reads the aegis_token cookie or a Bearer header, for use by the Caddy reverse proxy.
Dependencies
Python Packages
| Package | Version | Purpose |
|---|---|---|
fastapi | ^0.115 | Web framework |
uvicorn | ^0.34 | ASGI server |
pyjwt | ^2.8 | JWT encoding and decoding |
bcrypt | * | Password hashing and verification |
asyncpg | * | PostgreSQL access (via aegis-shared) |
python-multipart | ^0.0.22 | Form data support |
Infrastructure Dependencies
PostgreSQL — the auth service reads and writes the users table.
Configuration
| Environment Variable | Default | Description |
|---|---|---|
JWT_SECRET | aegis-local-dev-secret-change-in-production | HMAC secret for JWT signing |
DATABASE_URL | postgresql://aegis:aegis_local@localhost:5432/aegis | PostgreSQL connection for the users table |
BOOTSTRAP_ADMIN_EMAIL | admin@aegis.local | Seeded admin email on startup |
BOOTSTRAP_ADMIN_PASSWORD | aegis-dev-admin | Seeded admin password on startup |
The service binds to 0.0.0.0:8009 by default (hardcoded in __main__).
The JWT_SECRET must be identical across the auth service and the API gateway. If they differ, tokens issued by the auth service will fail validation at the gateway, and vice versa.
Running Locally
cd services/auth-service
poetry install
poetry run uvicorn auth_service.main:app --reload --port 8009Testing the Auth Flow
# Step 1: Exchange email + password for a JWT token
curl -X POST http://localhost:8009/auth/token \
-H "Content-Type: application/json" \
-d '{"email": "admin@aegis.local", "password": "aegis-dev-admin"}'
# Response:
# {
# "access_token": "eyJhbGciOiJIUzI1NiIs...",
# "token_type": "bearer",
# "expires_in": 86400,
# "user_id": "dev-user",
# "roles": ["admin", "operator", "reviewer"]
# }
# Step 2: Use the token in subsequent requests
curl http://localhost:8000/api/v1/conversations \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."