Skip to content

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:

  1. Create client aegisai (confidential, standard flow)
  2. Valid redirect URI: https://aegisai.example.com/api/v1/auth/sso/callback
  3. Add mappers for email, groups, or realm_access.roles as 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:

SSO_ROLE_MAPPING_JSON={"admin":["Corp-Admins","IT-Admins"],"super_admin":["Platform-Admins"]}

Claims read from: groups, roles, Keycloak realm_access.roles, and resource_access.

User Provisioning

On first SSO login AgentOps:

  1. Finds user by (auth_provider, external_subject) or links by email
  2. Creates user with password_hash = NULL (password login disabled)
  3. Assigns role from IdP groups
  4. 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

curl https://aegisai.example.com/api/v1/auth/sso/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:

docker compose exec app poetry run alembic upgrade head

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