SAML SSO Endpoints
GET /saml/:org_slug/:service_slug/metadata
Retrieve the implemented SAML Identity Provider metadata in XML format. Service Provider requirements vary; validate that the returned metadata contains the fields required by the exact product and version being integrated.
Authentication: None required (public endpoint)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Example Request:
curl https://sso.example.com/saml/acme-corp/main-app/metadata
Response (200 OK):
Content-Type: application/samlmetadata+xml
Response Body (XML):
<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://sso.example.com/saml/acme-corp/main-app">
<IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MIIDXTCCAkWgAwIBAgIJAKZ...</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.example.com/saml/acme-corp/main-app/sso"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.example.com/saml/acme-corp/main-app/sso"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.example.com/saml/acme-corp/main-app/slo"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.example.com/saml/acme-corp/main-app/slo"/>
</IDPSSODescriptor>
<Organization>
<OrganizationName xml:lang="en">Acme Corporation</OrganizationName>
<OrganizationDisplayName xml:lang="en">Acme Corporation</OrganizationDisplayName>
<OrganizationURL xml:lang="en">https://sso.example.com</OrganizationURL>
</Organization>
</EntityDescriptor>
Metadata Fields:
EntityDescriptor@entityID: IdP’s unique entity identifierKeyDescriptor: Public signing certificateNameIDFormat: Supported NameID formatsSingleSignOnService: SSO endpoint URLs for different SAML bindingsSingleLogoutService: SLO endpoint URLs for different SAML bindingsOrganization: Organization display information
Error Responses:
403 Forbidden: Organization is not active404 Not Found: Service not found400 Bad Request: SAML is not enabled for this service
Usage:
Many Service Providers can automatically import IdP metadata from this URL. Provide this URL during SAML configuration in the Service Provider’s admin panel.
Example (Salesforce):
- Navigate to Setup > Identity > Single Sign-On Settings
- Click “New from Metadata File”
- Enter metadata URL:
https://sso.example.com/saml/acme-corp/main-app/metadata - Salesforce automatically imports IdP configuration
GET/POST /saml/:org_slug/:service_slug/sso
SAML Single Sign-On endpoint. Receives a SAMLRequest from the Service Provider and initiates the authentication flow. Supports both HTTP-Redirect (GET) and HTTP-POST bindings.
Authentication: None required (public endpoint)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Query/Form Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
SAMLRequest |
string | Yes | Base64-encoded (optionally deflated) SAML AuthnRequest |
RelayState |
string | No | Opaque value to be returned to SP after authentication |
Example Request (HTTP-Redirect binding):
# This request is typically initiated by the Service Provider
# User's browser is redirected to:
https://sso.example.com/saml/acme-corp/main-app/sso?SAMLRequest=nZFBa4MwGIb%2FSsh...&RelayState=https://sp.example.com/resource
Response: 302 Redirect to authentication page
Authentication Flow:
-
Service Provider Initiates SSO:
- User clicks “Login” in third-party app (e.g., Salesforce)
- SP generates SAMLRequest and redirects user to this endpoint
-
IdP Processes SAMLRequest:
- Decodes and parses SAMLRequest XML
- Extracts request ID, issuer (SP entity ID), and ACS URL
- Validates destination matches expected SSO endpoint
- Creates SAML state record to track the authentication session
-
User is Redirected to Authentication Page:
- User is redirected to
/saml/:org_slug/:service_slug/authenticate?state={state_id} - Authentication page displays available login options
- User is redirected to
-
User Authenticates:
- User selects OAuth provider (GitHub/Google/Microsoft) or password login
- Standard SSO authentication flow completes (with MFA if enabled)
-
SAMLResponse Generated:
- After successful authentication, IdP generates signed SAMLResponse
- Response includes user attributes and InResponseTo reference
- User’s browser auto-submits SAMLResponse to SP’s ACS URL
-
Service Provider Validates:
- SP validates signature using IdP’s public certificate
- SP verifies InResponseTo matches original request ID
- User is logged into Service Provider
Error Responses:
400 Bad Request: Invalid or missing SAMLRequest, malformed XML403 Forbidden: Organization not active, SAML not enabled404 Not Found: Service not found
Security Features:
- SAMLRequest validation and parsing
- SAML state expires after 15 minutes
- InResponseTo correlation contributes to replay defense; public SAML abuse-case evidence is still incomplete
- Signature verification at Service Provider
- RelayState preservation maintains SP session context
GET /saml/:org_slug/:service_slug/authenticate
SAML authentication page that displays login options to the user during SP-initiated SSO flow. This page is automatically shown after SAMLRequest validation.
Authentication: None required (public endpoint)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
state |
string | Yes | SAML state ID from SSO initiation |
Example Request:
# User is automatically redirected to this page
https://sso.example.com/saml/acme-corp/main-app/authenticate?state=550e8400-e29b-41d4-a716-446655440000
Response: HTML page with login options
Page Features:
- Displays organization branding (logo and colors if configured)
- Shows available OAuth provider buttons (GitHub, Google, Microsoft)
- Password login form (if enabled)
- “Sign In to Continue” messaging
- Links include SAML state parameter for flow continuation
Error Responses:
400 Bad Request: Invalid or expired SAML state
User Experience:
- User arrives at authentication page
- Sees branded login interface (if branding configured)
- Clicks OAuth provider button (e.g., “Continue with GitHub”)
- Redirected to OAuth provider for authorization
- Returns to IdP after OAuth success
- IdP generates signed SAMLResponse
- User’s browser submits SAMLResponse to Service Provider
- User is logged into Service Provider
GET /api/organizations/:org_slug/services/:service_slug/saml/login
IdP-Initiated SAML SSO. Allows an authenticated user to directly initiate SSO to a Service Provider without a SAMLRequest (unsolicited SAML response).
Authentication: Required (Organization Management JWT)
Permissions: A current organization member or an authenticated identity scoped to the exact organization and service. Platform ownership alone does not grant assertion access.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_slug |
string | Organization slug |
service_slug |
string | Service slug |
Request Headers:
Authorization: Bearer {jwt_token}
Important Implementation Note: This endpoint requires JWT authentication and cannot be accessed via a simple HTML link. The frontend must make an authenticated XHR/Fetch request to retrieve the HTML form, then render/execute it in the browser.
Frontend Implementation Example:
async function initiateIdpLogin(orgSlug, serviceSlug, token) {
try {
const response = await fetch(
`/api/organizations/${orgSlug}/services/${serviceSlug}/saml/login`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'text/html'
}
}
);
if (response.ok) {
const htmlContent = await response.text();
// Render the HTML content in a new window or iframe
const popup = window.open('', '_blank');
popup.document.write(htmlContent);
}
} catch (error) {
console.error('Failed to initiate IdP login:', error);
}
}
Direct API Request (for testing):
curl -X GET https://sso.example.com/api/organizations/acme-corp/services/main-app/saml/login \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
Response: HTML page with auto-submitting form
Response Body (HTML):
<!DOCTYPE html>
<html>
<head>
<title>Signing you in...</title>
<style>
body { font-family: sans-serif; background: #f5f5f5; padding: 20px; text-align: center; }
.container { max-width: 400px; margin: 50px auto; background: white; padding: 40px; border-radius: 8px; }
.spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%;
width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body onload="document.forms[0].submit()">
<div class="container">
<div class="spinner"></div>
<h2>Signing you in...</h2>
<p>Please wait while we redirect you to the service.</p>
</div>
<form method="post" action="https://sp.example.com/acs" style="display: none;">
<input type="hidden" name="SAMLResponse" value="PHNhbWxwOlJlc3Bv..." />
<noscript>
<p>JavaScript is disabled. Please click the button below to continue.</p>
<input type="submit" value="Continue" />
</noscript>
</form>
</body>
</html>
Flow Differences from SP-Initiated:
SP-Initiated (standard):
- Service Provider sends SAMLRequest to IdP
- SAMLResponse includes
InResponseToattribute - Relay state from SP is preserved
IdP-Initiated:
- No SAMLRequest received by IdP
- SAMLResponse has NO
InResponseToattribute - No SAML state stored in database
- User must be pre-authenticated with IdP
Error Responses:
400 Bad Request: SAML not enabled, no ACS URL configured403 Forbidden: Organization not active404 Not Found: Organization or service not found500 Internal Server Error: No active signing key, encryption service unavailable
Use Cases:
- Dashboard portals where users click to access integrated services
- Quick access links to frequently used Service Providers
- Admin panels with “Login to Service” buttons
- Mobile apps with direct SSO capabilities
Security Notes:
- Requires active organization status
- Requires SAML enabled and configured
- Requires active signing certificate
- User must have valid authenticated session
- SAMLResponse signed according to service configuration
- Some Service Providers may not accept unsolicited responses (check SP documentation)