Skip to main content
Version: 2026.04.0

Custom / Generic SAML IdP

Connect any SAML 2.0-compliant identity provider that does not have a dedicated Primus provider page.

Common use cases:

  • ADFS (Active Directory Federation Services) — Microsoft's on-premises federation server, widely used in large enterprise Windows environments
  • Shibboleth — open-source SAML federation software standard in universities and research institutions
  • PingFederate — enterprise identity federation by Ping Identity

Provider SAML support:

  • Azure AD, Okta, Google Workspace — SAML presets available (IdpType: AzureAd, Okta, GoogleWorkspace). Their individual provider pages include dedicated SAML setup guides.
  • Auth0 — SAML IS supported by Auth0 as an IdP, but has no preset. Use IdpType: Custom with Auth0's metadata URL (see example below).
  • ADFS, Shibboleth, PingFederate, and any other SAML 2.0 IdP — use IdpType: Custom.

Support includes SP-initiated SSO, IdP-initiated SSO, and JIT user provisioning through IPrimusAuthUserStore.

Step 1: Install the package

dotnet add package PrimusSaaS.Identity.Broker

Step 2: Configure Program.cs

using PrimusSaaS.Identity.Broker;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());
builder.Services.AddControllers();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UsePrimusCsrfProtection();
app.MapControllers();
app.MapPrimusAuthBroker();
app.Run();

Step 3: Configure appsettings.json

With a metadata URL

If your IdP exposes a publicly accessible metadata URL:

{
"PrimusAuth": {
"Saml": {
"EntityId": "https://app.example.com",
"IdentityProviders": [
{
"Scheme": "enterprise-sso",
"DisplayName": "Corporate SSO",
"IdpType": "Custom",
"MetadataUrl": "https://adfs.company.com/FederationMetadata/2007-06/FederationMetadata.xml"
}
]
}
}
}

Auth0 SAML

Auth0 supports SAML as an Identity Provider. Enable the SAML2 addon in your Auth0 application dashboard (Applications → your app → Addons → SAML2 Web App) and set the callback URL to https://app.example.com/api/auth/saml/auth0-saml/Acs.

{
"Scheme": "auth0-saml",
"DisplayName": "Auth0 SSO",
"IdpType": "Custom",
"MetadataUrl": "https://YOUR_DOMAIN.auth0.com/samlp/metadata/YOUR_CLIENT_ID"
}

Auth0 uses http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress for the email claim by default — no override needed unless you have a custom rule.


Manual config (no metadata URL available)

If the IdP can't serve a publicly accessible metadata document:

{
"Scheme": "enterprise-sso",
"DisplayName": "Corporate SSO",
"IdpType": "Custom",
"SingleSignOnUrl": "https://sso.company.com/saml/sso",
"IdpEntityId": "https://sso.company.com",
"SigningCertificateBase64": "MIIC...base64encodedDERcert...==",
"EmailClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
}

The SigningCertificateBase64 is the DER-encoded certificate (base64) exported from the IdP.


Step 4: Configure endpoints

After app.MapPrimusAuthBroker(), each configured SAML IdP exposes:

EndpointDescription
GET /api/auth/saml/{scheme}Returns SP metadata XML (used by IdP during setup)
GET /api/auth/saml/{scheme}/SignInInitiates SP-initiated SSO — redirects browser to IdP
POST /api/auth/saml/{scheme}/AcsAssertion Consumer Service — handled automatically by Sustainsys.Saml2
GET /api/auth/providersLists all configured providers including SAML

For the enterprise-sso scheme above, the endpoints are:

  • GET /api/auth/saml/enterprise-sso/SignIn → redirects to the IdP login page (SP-initiated SSO)
  • GET /api/auth/saml/enterprise-sso → returns SP metadata XML — give this URL to your IdP during setup
  • POST /api/auth/saml/enterprise-sso/Acs → receives the SAML assertion post-authentication

Multiple SAML IdPs

You can register as many SAML IdPs as you need. Each needs a unique Scheme. All providers — Azure AD, Okta, Google Workspace, Auth0, ADFS, or any other — can coexist:

{
"PrimusAuth": {
"Saml": {
"EntityId": "https://app.example.com",
"IdentityProviders": [
{
"Scheme": "contoso",
"DisplayName": "Contoso Corp (Azure AD)",
"IdpType": "AzureAd",
"AzureTenantId": "contoso-tenant-id"
},
{
"Scheme": "fabrikam",
"DisplayName": "Fabrikam Inc (Okta)",
"IdpType": "Okta",
"MetadataUrl": "https://fabrikam.okta.com/app/xyz/sso/saml/metadata"
},
{
"Scheme": "auth0-saml",
"DisplayName": "Auth0 SSO",
"IdpType": "Custom",
"MetadataUrl": "https://YOUR_DOMAIN.auth0.com/samlp/metadata/YOUR_CLIENT_ID"
},
{
"Scheme": "corp-adfs",
"DisplayName": "Corporate ADFS",
"IdpType": "Custom",
"MetadataUrl": "https://adfs.company.com/FederationMetadata/2007-06/FederationMetadata.xml"
}
]
}
}
}

Each IdP gets its own login endpoint — e.g., GET /api/auth/saml/contoso, GET /api/auth/saml/auth0-saml, GET /api/auth/saml/corp-adfs.


User provisioning

SAML sign-ins use the same IPrimusAuthUserStore JIT provisioning as OIDC providers — one interface handles all login methods. The provider parameter passed to AutoProvisionUserAsync identifies how the user authenticated. For OIDC providers it is the provider name ("azure", "google", "auth0", "okta"). For SAML it is always "saml:" followed by the Scheme you configured — so Scheme: "azure-saml" produces provider = "saml:azure-saml" and Scheme: "enterprise-sso" produces provider = "saml:enterprise-sso".

builder.Services.AddScoped<IPrimusAuthUserStore, MyUserStore>();

// In MyUserStore:
public async Task<PrimusAuthUser?> AutoProvisionUserAsync(
string email,
string provider, // "saml:azure-saml", "saml:okta-saml", etc.
ClaimsPrincipal samlPrincipal,
CancellationToken cancellationToken)
{
if (provider.StartsWith("saml:"))
{
// SAML-specific provisioning — pull extra attributes from the assertion
var department = samlPrincipal
.FindFirst("http://schemas.xmlsoap.org/claims/Department")?.Value;

return await _db.ProvisionUserAsync(email, department, cancellationToken);
}
return null;
}

Email claim mapping

By default, the broker looks for the SAML email attribute at:

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress

This URI is the standard claim name used by ADFS and Azure AD SAML — it comes from the WS-Federation specification namespace and is used purely as a stable string identifier (no SOAP calls happen). Google Workspace SAML uses the attribute name email directly instead.

If your IdP uses a different attribute, override it per IdP:

{
"Scheme": "adfs-sso",
"EmailClaimType": "http://schemas.company.com/claims/email"
}

IdP-initiated SSO

All SAML IdPs are configured with AllowUnsolicitedAuthResponse: true by default, enabling IdP-initiated flows where the user clicks a tile in the IdP's portal without first visiting your app.

To disable IdP-initiated SSO for a specific IdP:

{
"Scheme": "strict-saml",
"AllowUnsolicitedAuthResponse": false
}

Configuration reference

PropertyRequiredAllowed valuesDescription
PrimusAuth:Saml:EntityIdYes (if any SAML IdP is configured)Any URL/URI, e.g. https://app.example.comSP Entity ID — your app's base URL
SchemeYesURL-safe string, e.g. azure-saml, corp-ssoShort key used in login and callback URLs. Must be unique per IdP.
DisplayNameOptionalAny stringHuman-readable name shown in provider lists
IdpTypeOptionalCustom (default), AzureAd, Okta, GoogleWorkspacePre-set that auto-resolves metadata URL and entity ID for well-known providers
AzureTenantIdRequired if IdpType: AzureAdAzure tenant GUIDAuto-resolves the generic Azure tenant federation metadata URL and default IdP entity ID.
MetadataUrlRecommendedHTTPS URLIdP federation metadata URL — auto-fetches and refreshes signing certificates. For Azure Enterprise Application SAML, use the explicit App Federation Metadata Url when tenant-level metadata is not sufficient.
SingleSignOnUrlRequired if no MetadataUrlHTTPS URLIdP SSO endpoint for manual config
IdpEntityIdRequired if no MetadataUrlURI stringIdP entity ID for manual config
SigningCertificateBase64Optional, manual config onlyBase64-encoded DER certificateIdP signing certificate. Not needed when using MetadataUrl.
EmailClaimTypeOptionalURI or attribute nameSAML attribute name for the user's email. Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
AllowUnsolicitedAuthResponseOptionaltrue / falseAllow IdP-initiated SSO (unsolicited assertions). Default: true

For Azure specifically, AzureTenantId is enough for many setups because the broker can derive the tenant metadata URL internally. If Azure SAML certificate validation does not line up with that generic metadata, set MetadataUrl explicitly to the App Federation Metadata Url from the Azure Enterprise Application.


Next Steps

  • Integration Guide — complete setup walkthrough from install to first authenticated request
  • Endpoint Reference — complete request and response shapes for SAML and all other endpoints
  • MFA Deep Dive — add multi-factor authentication on top of SAML SSO
  • Database Setup — persist provisioned SAML users with EF Core