Single Sign-On (SSO)¶
AgentOps supports enterprise login through external Identity Providers (IdP) via OpenID Connect (OIDC).
Supported Providers¶
| Provider | Protocol | Configuration |
|---|---|---|
| Keycloak | OIDC | Self-hosted, full control |
| Okta | OIDC | Cloud IdP |
| Google Workspace | OIDC | Small teams |
| Microsoft Entra ID | OIDC | Corporate Azure AD / Entra |
SAML 2.0
SAML SSO is supported with cryptographic verification via signxml. Enable with SAML_* environment variables (see below). OIDC remains the recommended path for most IdPs.
Authentication Flow¶
sequenceDiagram
participant User
participant FE as Frontend
participant API as AgentOps API
participant IdP as Keycloak/Okta/...
User->>FE: Click "Sign in with SSO"
FE->>API: GET /auth/sso/login?provider=keycloak
API->>IdP: Redirect (authorization code + state)
User->>IdP: Enter credentials
IdP->>API: GET /auth/sso/callback?code=...&state=...
API->>IdP: Exchange code for tokens
API->>API: Verify id_token (JWKS), provision user
API->>FE: Redirect /login?sso_code=...
FE->>API: POST /auth/sso/exchange
API-->>FE: JWT + api_key + user
FE->>FE: Store session, navigate to dashboard
Security properties:
- state and nonce protect against CSRF and replay
- id_token signature verified via IdP JWKS
- JWT is not passed in the URL — a one-time exchange code (Redis, 120s TTL) is used instead
Enable SSO¶
1. Global settings¶
SSO_PUBLIC_BASE_URL=https://aegisai.example.com
SSO_FRONTEND_URL=https://aegisai.example.com
SSO_CALLBACK_URL=https://aegisai.example.com/api/v1/auth/sso/callback
SSO_DEFAULT_PROVIDER=keycloak
SSO_AUTO_PROVISION=true
SSO_SYNC_ROLES=true
| Variable | Description |
|---|---|
SSO_PUBLIC_BASE_URL |
Public URL of the site (used for default callback) |
SSO_FRONTEND_URL |
Frontend origin for post-login redirect |
SSO_CALLBACK_URL |
OIDC redirect URI registered in IdP |
SSO_AUTO_PROVISION |
Create users on first SSO login |
SSO_SYNC_ROLES |
Update role from IdP groups on each login |
SSO_ROLE_MAPPING_JSON |
Custom group → role mapping (JSON) |
2. Keycloak¶
KEYCLOAK_ENABLED=true
KEYCLOAK_CLIENT_ID=aegisai
KEYCLOAK_CLIENT_SECRET=your-client-secret
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/master
In Keycloak admin:
- Create client
aegisai(confidential, standard flow) - Valid redirect URI:
https://aegisai.example.com/api/v1/auth/sso/callback - Add mappers for
email,groups, orrealm_access.rolesas needed
3. Okta¶
OKTA_ENABLED=true
OKTA_CLIENT_ID=your-client-id
OKTA_CLIENT_SECRET=your-client-secret
OKTA_ISSUER=https://your-org.okta.com/oauth2/default
Register the same callback URL in the Okta application settings.
4. Google Workspace¶
GOOGLE_SSO_ENABLED=true
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
Authorized redirect URI: https://aegisai.example.com/api/v1/auth/sso/callback
5. Microsoft Entra ID¶
MICROSOFT_SSO_ENABLED=true
MICROSOFT_TENANT_ID=your-tenant-id
MICROSOFT_CLIENT_ID=your-application-id
MICROSOFT_CLIENT_SECRET=your-client-secret
Redirect URI (Web): https://aegisai.example.com/api/v1/auth/sso/callback
Role Mapping¶
Default mapping from IdP groups/roles to AgentOps roles:
| AgentOps Role | Example IdP groups |
|---|---|
super_admin |
super_admin, aegis-super-admin |
admin |
admin, Admins, Administrator |
security_analyst |
SecurityAnalysts, security_analyst |
developer |
Developers, developer |
viewer |
default if no match |
Custom mapping:
Claims read from: groups, roles, Keycloak realm_access.roles, and resource_access.
User Provisioning¶
On first SSO login AgentOps:
- Finds user by
(auth_provider, external_subject)or links by email - Creates user with
password_hash = NULL(password login disabled) - Assigns role from IdP groups
- Auto-binds to the sole tenant when unambiguous (same as password login)
Set SSO_AUTO_PROVISION=false to allow SSO only for pre-created users.
API Endpoints¶
| Method | Endpoint | Description |
|---|---|---|
GET |
/auth/sso/providers |
List enabled IdP (public) |
GET |
/auth/sso/login?provider=... |
Start OIDC flow (redirect) |
GET |
/auth/sso/callback |
IdP callback (redirect) |
POST |
/auth/sso/exchange |
Exchange one-time code for JWT |
List providers¶
Exchange code (frontend)¶
curl -X POST https://aegisai.example.com/api/v1/auth/sso/exchange \
-H "Content-Type: application/json" \
-d '{"code": "<sso_code from redirect>"}'
Database Migration¶
SSO requires migration 006_sso_fields:
Adds auth_provider, external_subject, and nullable password_hash on users.
Troubleshooting¶
| Issue | Solution |
|---|---|
| No SSO buttons on login | Check provider *_ENABLED=true and client_id set; call /auth/sso/providers |
invalid_state |
Clock skew, expired flow (>10 min), or cookies blocked — retry login |
redirect_uri mismatch |
Callback URL in IdP must exactly match SSO_CALLBACK_URL |
User created as viewer |
Add group mappers in IdP; configure SSO_ROLE_MAPPING_JSON |
| SSO user cannot use password | Expected — SSO users have no local password |
Related Documentation¶
- Users — user management
- Roles — RBAC matrix
- Deployment — production env vars
- Troubleshooting — login issues