Identity Providers (SSO)
Admin-only CRUD for external identity providers (OIDC, LDAP and SAML 2.0). Secrets are write-only: responses carry secret_set (boolean) and never the value; an empty secret on update keeps the stored one.
SAML config keys (type: "saml"): idp_metadata_url or idp_metadata_xml (one required), group_attr (assertion attribute carrying groups; default groups), allow_private_network (for metadata fetch from an RFC1918 IdP). The SP signing keypair is generated on first use and stored (secret = private key PEM, config.sp_certificate = cert PEM). Responses include metadata_uri (the SP metadata endpoint) alongside redirect_uri (the ACS). SAML providers appear as login-page buttons. The test endpoint runs external_url/sp_keypair/idp_metadata/sso_endpoint checks.
LDAP config keys (type: "ldap"; secret holds the bind password): url (ldap:///ldaps://, required), base_dn (required), start_tls, insecure_skip_verify, bind_dn (empty = anonymous search), user_filter (%s = username; default (|(uid=%s)(sAMAccountName=%s))), group_attr (default memberOf), group_filter (%s = user DN, for directories without a memberOf overlay), nested_groups (AD matching-rule-in-chain), id_attr (identity attribute; default auto: objectGUID → entryUUID — never the DN), allow_private_network. LDAP providers never appear as login-page buttons — directory accounts sign in through the username/password form. The test endpoint runs connect/tls/bind/base checks (plain ldap:// without StartTLS fails the tls check).
List / Get Providers
GET /api/v1/admin/auth-providers
GET /api/v1/admin/auth-providers/{providerID}
Response: 200 OK
{
"id": "uuid",
"type": "oidc",
"name": "Corporate SSO",
"enabled": true,
"config": { "issuer": "https://idp.example.com", "client_id": "osprey", "scopes": "openid profile email groups", "groups_claim": "groups", "groups_via_userinfo": false, "allow_private_network": false },
"role_mapping": [ { "group": "netops-admins", "role": "admin" } ],
"default_role": "",
"secret_set": true,
"redirect_uri": "https://osprey.example.com/api/v1/auth/oidc/<id>/callback",
"created_at": "2026-07-18T00:00:00Z",
"updated_at": "2026-07-18T00:00:00Z"
}
redirect_uri is derived from auth.external_url (absent while that setting is empty) — register it at the IdP. default_role: "" means deny when no mapping matches. allow_private_network disables the SSRF dial-guard for this provider only (required for on-premises IdPs on RFC1918 space).
Create / Update Provider
POST /api/v1/admin/auth-providers
PUT /api/v1/admin/auth-providers/{providerID}
Body: {type, name, enabled, config, role_mapping, default_role, secret}. type is immutable after create. Validation: type ∈ oidc/saml/ldap; name required (≤100, unique); OIDC requires config.issuer + config.client_id; every role_mapping entry needs a group and a valid role. enabled is merge-only on update: an omitted field keeps the stored state; disabling a provider revokes the sessions and refresh tokens of every user who signed in through it. On create, an omitted enabled means disabled.
Error responses: 400 validation, 409 duplicate name, 404 unknown provider.
Toggle / Delete Provider
POST /api/v1/admin/auth-providers/{providerID}/toggle
DELETE /api/v1/admin/auth-providers/{providerID}
Toggle flips enabled; disabling revokes the provider's users' sessions. Delete additionally deactivates accounts whose only identity was this provider (orphan policy — no login-impossible ghost accounts stay active) and cascades the identity links. Both run in one transaction. Toggle returns 200 {"enabled": bool}; delete returns 204.
Test Provider
POST /api/v1/admin/auth-providers/{providerID}/test
Runs per-layer connectivity checks and returns them individually (never a bare OK/FAIL):
{
"overall": "fail",
"checks": [
{ "id": "tls", "label": "Issuer uses HTTPS", "status": "fail", "detail": "issuer is not an https:// URL" },
{ "id": "discovery", "label": "OIDC discovery document", "status": "ok", "detail": "issuer https://..." },
{ "id": "jwks", "label": "JWKS reachable", "status": "ok", "detail": "2 key(s) at https://..." }
]
}
status is ok, fail or skip; overall is fail when any check fails.