Authentication and Security
This page covers the full authentication model, token management, API key usage, SSO configuration, and security controls in Aegis.
Authentication Methods
Aegis supports two authentication methods, checked in order:
1. HttpOnly Cookie (Primary — Browser Use)
After a successful login, the server sets:
Set-Cookie: aegis_token=<JWT>; HttpOnly; SameSite=Strict; Path=/; [Secure in prod]
The browser automatically sends this cookie on every request. This is the recommended method for the frontend.
2. Authorization: Bearer Header (API / Machine Use)
Authorization: Bearer <JWT>
Use this for:
- API integrations
- Scripts and CI/CD pipelines
- When cookies are not available (e.g. cross-origin API calls)
JWT Token
Structure
The JWT payload contains:
{
"sub": "user-uuid",
"role": "red_lead",
"jti": "unique-token-id",
"exp": 1710000000,
"iat": 1709996400
}
Expiry
Configurable via environment variable:
ACCESS_TOKEN_EXPIRE_MINUTES=480 # 8 hours default
Token Revocation (Blacklist)
On logout, the token's jti is stored in Redis with TTL = remaining token lifetime:
Redis key: blacklist:{jti}
Value: "1"
TTL: <seconds until token expiry>
Every authenticated request checks Redis for the jti. If found → 401 Unauthorized.
Cookie Security
| Setting | Development | Production |
|---|---|---|
HttpOnly |
True | True |
SameSite |
Strict | Strict |
Secure |
False | True |
Path |
/ | / |
SECURE_COOKIES environment variable:
auto(default):Secure=TruewhenAEGIS_ENV=productiontrue: Always setSecureflagfalse: Never setSecureflag (development only)
Password Policy
- Minimum length: 12 characters
- Must contain: uppercase, lowercase, digit, and special character
- Enforced at account creation and password change
- Passwords are hashed with bcrypt (work factor 12)
must_change_password
When an admin creates a user, must_change_password is set to True.
Until the user changes their password, every endpoint returns:
{"detail": "PASSWORD_CHANGE_REQUIRED"}
with status 403, except:
GET /api/v1/auth/mePOST /api/v1/auth/change-password
Change password:
POST /api/v1/auth/change-password
Content-Type: application/json
{
"current_password": "admin-set-password",
"new_password": "MyNewSecurePassword123!"
}
API Keys
API keys provide long-lived credentials for machine-to-machine access without session management.
Creating a Key
POST /api/v1/api-keys
Content-Type: application/json
{
"name": "CI Pipeline Key",
"scope": "read",
"expires_at": "2025-12-31T00:00:00Z"
}
Response includes the key value (only shown once):
{
"id": "uuid",
"name": "CI Pipeline Key",
"key": "aegis_a1b2c3d4e5f6...",
"scope": "read"
}
Using a Key
Authorization: Bearer aegis_a1b2c3d4e5f6...
Scopes
| Scope | Allowed HTTP methods |
|---|---|
read |
GET, HEAD, OPTIONS only |
write |
GET, POST, PATCH, PUT, DELETE |
admin |
All + admin-only endpoints |
A read scope key that attempts POST/PATCH/DELETE receives:
{"detail": "API key scope 'read' does not permit this operation"}
with status 403.
Revoking a Key
DELETE /api/v1/api-keys/{id}
SAML 2.0 SSO
Aegis supports SAML 2.0 for enterprise single sign-on (e.g. Azure AD, Okta, Ping).
Configuration (admin only)
PUT /api/v1/sso/config
Content-Type: application/json
{
"enabled": true,
"idp_entity_id": "https://idp.example.com",
"idp_sso_url": "https://idp.example.com/sso/saml",
"idp_certificate": "-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----",
"sp_entity_id": "https://aegis.example.com",
"attribute_mapping": {
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
"role": "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"
}
}
SSO Login Flow
- Browser visits
GET /api/v1/sso/login→ redirect to IdP - User authenticates with IdP
- IdP POSTs SAML assertion to
POST /api/v1/sso/callback - Aegis validates assertion, finds/creates user, issues JWT cookie
- Browser redirected to frontend
Service Provider Metadata
GET /api/v1/sso/metadata
Returns XML metadata to register Aegis as a SAML service provider in your IdP.
Rate Limiting
- Login endpoint: 5 attempts per minute per IP
- Exceeded:
429 Too Many Requests - Implemented via Redis counters with TTL
SSRF Protection (Webhooks)
Webhook URLs are validated before saving. The following IP ranges are blocked:
| Range | Description |
|---|---|
| 10.0.0.0/8 | Private (Class A) |
| 172.16.0.0/12 | Private (Class B) |
| 192.168.0.0/16 | Private (Class C) |
| 169.254.0.0/16 | Link-local / APIPA |
| 127.0.0.0/8 | Loopback |
| ::1/128 | IPv6 loopback |
| fc00::/7 | IPv6 unique local |
| 0.0.0.0/8 | Reserved |
| 100.64.0.0/10 | Shared address space |
Attempting to create a webhook pointing to a private IP returns:
{"detail": "Webhook URL points to a private or reserved IP address (SSRF prevention)"}
Audit Logging
Every significant action is recorded in the audit_logs table (admin read-only):
| Event type | Logged when |
|---|---|
auth.login |
Successful login |
auth.logout |
Logout |
auth.login_failed |
Failed login attempt |
auth.password_changed |
Password change |
user.created |
New user created |
user.updated |
User updated |
user.deleted |
User deleted |
test.created |
New test created |
test.state_changed |
Test state transition |
test.validated |
Both leads approved |
campaign.completed |
Campaign completed |
permission.denied |
403 on a protected endpoint |
Query audit logs:
GET /api/v1/audit-logs?user_id=<id>&event_type=auth.login&from=2024-01-01&to=2024-12-31