Skip to main content

SSO endpoints

/sso/* group + test/validation routes on /platform/organizations/{id}/identity-providers/{id}/…. Supports OIDC (production) and SAML (preview).

Provider configuration is done via identity providers endpoints. The endpoints on this page execute the login/logout flow.

Endpoint table

MethodPathAuthDescription
GET/sso/oidc/{provider_id}/loginPublicStarts OIDC flow. Returns the IdP URL.
POST/sso/discoveryPublicDiscovers the provider configured for an email/domain.
GET/sso/oidc/callbackPublic (validated via state)Callback after authenticating at the IdP.
GET/sso/saml/sp-configPublicService Provider config (to use on the IdP).
GET/sso/saml/metadataPublicSP metadata XML to import in the IdP.
POST/sso/saml/acsCurrently returns 501 Not Implemented.
POST/sso/logoutClears the session cookie and redirects.
POST/platform/organizations/{organization_id}/identity-providers/{provider_id}/testSessionTriggers a test OIDC flow.
POST/platform/organizations/{organization_id}/identity-providers/{provider_id}/validateSessionValidates the config (discovery + token test) without running the full flow.

OIDC flow (end user)

GET /sso/oidc/{provider_id}/login

Starts the login. Returns (JSON) the URL the frontend should redirect to.

Query params:

  • redirect_path — absolute path (starts with /) to return to after authenticating. Default: root.

Response 200:

{
"authorization_url": "https://idp.acme.com/oauth2/v1/authorize?client_id=…&state=…",
"state": "opaque-token"
}

The frontend redirects to authorization_url. The state is stored in Redis (TTL configurable via NATIVE_WAZUH_SSO_STATE_TTL_SECONDS, default 600 s) to protect against CSRF on the callback.

POST /sso/discovery

Given an email or domain, tells which identity provider is configured. Used by the login screen to decide whether to show "Continue with Okta", "Continue with Azure AD", etc.

Request body (SsoDiscoveryRequest):

{ "email": "alice@acme.com" }

Response 200 (SsoDiscoveryResponse):

{
"provider_id": "idp_...",
"name": "Corporate Okta",
"kind": "oidc",
"login_url": "/api/v1/sso/oidc/idp_.../login"
}

When no provider is linked to the domain, returns { "provider_id": null } — the frontend falls back to traditional login.

GET /sso/oidc/callback

Endpoint the IdP calls back after authentication. Do not call manually — invoked by the IdP's OAuth2/OIDC flow itself.

Query params (sent by the IdP):

  • provider_id — target provider (part of the redirect_uri registered at the IdP).
  • code — authorization code.
  • state — token to validate CSRF.

Behavior:

  1. Validates state against what was stored in Redis.
  2. Exchanges code for tokens at the IdP (token_endpoint from discovery).
  3. Reads the userinfo or id_token to extract email/claims.
  4. Creates or links the user to the provider's organization.
  5. Opens a session (cookie).
  6. Redirects to NATIVE_WAZUH_PUBLIC_BASE_URL + original redirect_path.

Response: 302 Found with Set-Cookie: ruleforge_session=….

POST /sso/logout

Clears the session cookie and redirects to the public home.

Response: 302 Found to NATIVE_WAZUH_PUBLIC_BASE_URL.

Currently does not trigger SLO (Single Logout) at the IdP — only clears the local session. SLO is tracked for future releases.

SAML (preview)

SAML is in preview. The endpoints below exist so IdPs can be configured, but processing of the SAMLResponse (ACS) is not implemented yet.

GET /sso/saml/sp-config

Response 200 (SamlServiceProviderConfig):

{
"entity_id": "https://ruleforge.example.com/sso/saml",
"acs_url": "https://ruleforge.example.com/api/v1/sso/saml/acs",
"slo_url": "https://ruleforge.example.com/api/v1/sso/logout",
"name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
}

GET /sso/saml/metadata

Response 200Content-Type: application/samlmetadata+xml:

<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="https://ruleforge.example.com/sso/saml">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://ruleforge.example.com/api/v1/sso/saml/acs"
index="1" isDefault="true" />
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://ruleforge.example.com/api/v1/sso/logout" />
</SPSSODescriptor>
</EntityDescriptor>

Import this XML in the SAML IdP to register RuleForge as an SP.

POST /sso/saml/acs

Response 501SAML is still in preview. The ACS endpoint is not yet available for production use.

Test and validate (admin)

POST /platform/organizations/{organization_id}/identity-providers/{provider_id}/test

Triggers a test OIDC flow without affecting real sessions — useful for debugging configuration.

Query params: redirect_path (optional).

Response 200: returns the IdP URL + a test state.

POST /platform/organizations/{organization_id}/identity-providers/{provider_id}/validate

Performs the following checks without UI:

  1. Resolves the discovery_url.
  2. Confirms authorization_endpoint, token_endpoint and jwks_uri respond.
  3. Confirms the client_id is accepted (optional, IdP-dependent).

Response 200:

{
"valid": true,
"issuer": "https://acme.okta.com",
"authorization_endpoint": "https://acme.okta.com/oauth2/v1/authorize",
"token_endpoint": "https://acme.okta.com/oauth2/v1/token",
"jwks_uri": "https://acme.okta.com/oauth2/v1/keys",
"warnings": [],
"errors": []
}

When valid=false, the errors list explains what failed.

Shared structures

SsoDiscoveryRequest / SsoDiscoveryResponse

// request
{ "email": "string" }

// response
{
"provider_id": "string|null",
"name": "string|null",
"kind": "oidc|saml|null",
"login_url": "string|null"
}

SamlServiceProviderConfig

{
"entity_id": "string",
"acs_url": "string",
"slo_url": "string",
"name_id_format": "string"
}