SAML SSO ​
This guide documents AYB's shipped SAML surface for service-provider (SP) login flow and admin provider management.
Source of truth:
internal/auth/saml.go,internal/auth/handler_oauth.gointernal/server/saml_handler.go,internal/server/routes_auth.go,internal/server/routes_admin.gointernal/config/config_types.go::SAMLProvider,internal/config/config_validate_auth.gointernal/config/config_default_toml.goSAML examples- Tests:
internal/auth/saml_test.go,internal/auth/saml_signature_test.go,internal/server/saml_handler_test.go,internal/config/config_test.go
What AYB supports ​
- SP-initiated login redirect:
GET /api/auth/saml/{provider}/login - ACS callback handling:
POST /api/auth/saml/{provider}/acs - SP metadata endpoint:
GET /api/auth/saml/{provider}/metadata - Admin CRUD for providers in
_ayb_saml_providers - Metadata source via either URL fetch or inline XML in admin CRUD flows
- Attribute mapping for
emailandnameduring JIT login
Config-based providers ​
auth.saml_providers entries (SAMLProvider):
enablednameentity_ididp_metadata_urlidp_metadata_xmlattribute_mappingsp_cert_filesp_key_file
Validation rules (config_validate_auth.go):
auth.enabledmust be true for enabled SAML providersnamerequiredentity_idrequired- one of
idp_metadata_urloridp_metadata_xmlrequired - enabled provider names must be unique
Current runtime behavior note:
- Config validation accepts either
idp_metadata_urloridp_metadata_xml. - Startup registration path (
routes_auth.go->SAMLService.UpsertProvider) currently consumesidp_metadata_xml; it does not fetch metadata fromidp_metadata_url. - URL-based metadata fetch is implemented in the admin SAML create/update API (
resolveSAMLMetadata).
For config-based startup registration today, resolve/export the IdP metadata first and paste the XML into idp_metadata_xml.
Example config:
[auth]
enabled = true
jwt_secret = "replace-with-32+-char-secret"
[[auth.saml_providers]]
enabled = true
name = "okta"
entity_id = "https://api.example.com/api/auth/saml/okta/metadata"
idp_metadata_xml = """
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<!-- Paste the IdP metadata document here for startup registration. -->
</EntityDescriptor>
"""
# idp_metadata_url = "https://dev-123456.okta.com/app/xyz/sso/saml/metadata"
# sp_cert_file = ""
# sp_key_file = ""
[auth.saml_providers.attribute_mapping]
email = "email"
name = "name"
groups = "groups"Provider-name format is validated by ValidateSAMLProviderName:
- regex:
^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$ - no slashes, dots, or path traversal patterns
IdP setup patterns ​
AYB supports any SAML 2.0 IdP that exposes metadata. Common setups:
- Okta: use the app metadata URL in the admin API, or export/paste the metadata XML for config-based startup
- Azure AD: use the Enterprise App federation metadata URL, or export/paste the metadata XML for config-based startup
- Google Workspace: use the metadata URL/XML from SAML app settings; config-based startup currently needs inline XML
In all cases, set the ACS URL in the IdP to:
https://<your-ayb-host>/api/auth/saml/<provider>/acs
And set SP entity ID to your configured entity_id.
SP metadata exchange ​
Fetch metadata:
curl http://localhost:8090/api/auth/saml/okta/metadataReturned XML includes:
<EntityDescriptor entityID="...">- signing cert (from generated or configured SP cert)
- ACS location
/api/auth/saml/{provider}/acs
If sp_cert_file / sp_key_file are not provided, AYB generates provider-scoped files in ~/.ayb/saml/ (or temp fallback if that path is not writable).
Auth flow ​
1) Login redirect ​
curl -i "http://localhost:8090/api/auth/saml/okta/login?redirect_to=https://app.example.com/post-auth"Behavior:
- AYB creates an AuthnRequest and stores request state for 5 minutes
- sets
ayb_saml_req_<provider>HttpOnly cookie - redirects to IdP SSO URL with
SAMLRequest(+ optionalRelayState)
2) ACS callback ​
IdP POSTs to /api/auth/saml/{provider}/acs with SAMLResponse.
AYB validates request/provider state, assertion validity window, then calls auth JIT login via:
- provider key:
saml:<provider> ProviderUserID: assertion subject (fallback email)Email: mapped email attributeName: mapped name attribute
Current implementation detail:
- AYB validates request binding (
request_id+ provider), request TTL, and assertion time bounds (NotBefore,NotOnOrAfter). - AYB verifies the signature of the exact identity-bearing assertion it consumes against the IdP signing certificates from provider metadata, and rejects any assertion whose signature is missing, malformed, signed by the wrong key, or otherwise unverifiable. Identity attributes are decoded only from the signature-verified element.
- AYB requires SHA-256-or-stronger XML signature and reference-digest algorithms, accepts IdP signing keys only at RSA-2048 / ECDSA P-256 strength or higher, and binds the verified assertion issuer to the IdP
entityIDin provider metadata. - The metadata signing certificates act as direct trust anchors, not as leaves of a separately validated public certificate chain. When provider metadata publishes more than one eligible signing certificate, any of the overlapping certificates can verify an assertion, which is what lets an IdP rotate signing keys without downtime.
- Because trust is anchored in the parsed metadata, replacing IdP signing material requires a provider metadata refresh / re-registration so AYB picks up the new certificates.
Response shape:
- JSON
{token, refreshToken, user}whenauth.oauth_redirect_urlis empty and no MFA step is pending - JSON
{mfa_pending, mfa_token}whenauth.oauth_redirect_urlis empty and the login requires MFA completion - redirect with token fragment when
auth.oauth_redirect_urlis set and no MFA step is pending - redirect with
#mfa_pending=true&mfa_token=...fragment data whenauth.oauth_redirect_urlis set and MFA is pending
Admin provider management API ​
Routes (admin token required):
GET /api/admin/auth/samlPOST /api/admin/auth/samlPUT /api/admin/auth/saml/{name}DELETE /api/admin/auth/saml/{name}
Dependency behavior:
- Returns
404 auth SAML is not enabledwhen auth/SAML services are not wired. - Returns
503 database is not configuredwhen the database pool is unavailable.
Create request fields:
nameentity_ididp_metadata_urloridp_metadata_xmlattribute_mapping
Update request fields:
entity_ididp_metadata_urloridp_metadata_xmlattribute_mapping
For updates, the provider name comes from the route path: PUT /api/admin/auth/saml/{name}.
Metadata URL fetches use a 10-second timeout and a 1 MiB response cap.
Create example:
curl -X POST http://localhost:8090/api/admin/auth/saml \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "okta",
"entity_id": "https://api.example.com/api/auth/saml/okta/metadata",
"idp_metadata_url": "https://dev-123456.okta.com/app/xyz/sso/saml/metadata",
"attribute_mapping": {
"email": "email",
"name": "displayName",
"groups": "memberOf"
}
}'Important current limitation ​
Group mappings are stored and configurable (attribute_mapping.groups in config/admin API), but SAMLService.HandleCallback currently uses mapped email and name values for JIT login and does not automatically apply group membership to org/team roles.