Skip to Content

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:

  1. The client authenticates with an email and password via the auth service, receiving a JWT token
  2. All subsequent requests include the JWT token in the Authorization: Bearer header (or the aegis_token cookie)
  3. 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

PropertyValue
Port8009
LanguagePython 3.12
FrameworkFastAPI
Entry pointsrc/auth_service/main.py

Key Endpoints

MethodPathDescription
POST/auth/tokenExchange email + password for a JWT token.
POST/auth/validateValidate a JWT token. Returns user_id and roles if valid.
*/auth/forward-authMethod-agnostic check of the aegis_token cookie or Bearer header (used by Caddy).
GET/healthHealth check.

Architecture

src/auth_service/ ├── __init__.py ├── main.py # Endpoints, JWT logic, password verification └── create_user.py # CLI for provisioning/rotating users

Authentication 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 requests

Invalid 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 }
FieldDescription
user_idThe authenticated user’s identifier
emailThe authenticated user’s email address
rolesArray of role strings (used for RBAC in the gateway)
issIssuer (aegis-auth)
iatIssued-at timestamp
expExpiration 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:

ColumnTypeNotes
idUUIDPrimary key, uuid_generate_v4()
emailVARCHAR(255)Not null; case-insensitive unique via idx_users_email_lower
password_hashTEXTNot null; bcrypt hash
rolesTEXT[]Not null, default ARRAY['operator']
display_nameVARCHAR(200)Optional
is_activeBOOLEANNot null, default TRUE
created_at / updated_attimestamps

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,reviewer

The 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.

EmailPasswordRoles
admin@aegis.localaegis-dev-adminadmin, 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:

  1. Strip the Bearer prefix if present
  2. Decode using PyJWT with the HS256 algorithm and the shared JWT_SECRET
  3. Return valid: true with the user’s identity, or valid: false if 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

PackageVersionPurpose
fastapi^0.115Web framework
uvicorn^0.34ASGI server
pyjwt^2.8JWT encoding and decoding
bcrypt*Password hashing and verification
asyncpg*PostgreSQL access (via aegis-shared)
python-multipart^0.0.22Form data support

Infrastructure Dependencies

PostgreSQL — the auth service reads and writes the users table.

Configuration

Environment VariableDefaultDescription
JWT_SECRETaegis-local-dev-secret-change-in-productionHMAC secret for JWT signing
DATABASE_URLpostgresql://aegis:aegis_local@localhost:5432/aegisPostgreSQL connection for the users table
BOOTSTRAP_ADMIN_EMAILadmin@aegis.localSeeded admin email on startup
BOOTSTRAP_ADMIN_PASSWORDaegis-dev-adminSeeded 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 8009

Testing 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..."
Last updated on