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: Customwith 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:
| Endpoint | Description |
|---|---|
GET /api/auth/saml/{scheme} | Returns SP metadata XML (used by IdP during setup) |
GET /api/auth/saml/{scheme}/SignIn | Initiates SP-initiated SSO — redirects browser to IdP |
POST /api/auth/saml/{scheme}/Acs | Assertion Consumer Service — handled automatically by Sustainsys.Saml2 |
GET /api/auth/providers | Lists 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 setupPOST /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
| Property | Required | Allowed values | Description |
|---|---|---|---|
PrimusAuth:Saml:EntityId | Yes (if any SAML IdP is configured) | Any URL/URI, e.g. https://app.example.com | SP Entity ID — your app's base URL |
Scheme | Yes | URL-safe string, e.g. azure-saml, corp-sso | Short key used in login and callback URLs. Must be unique per IdP. |
DisplayName | Optional | Any string | Human-readable name shown in provider lists |
IdpType | Optional | Custom (default), AzureAd, Okta, GoogleWorkspace | Pre-set that auto-resolves metadata URL and entity ID for well-known providers |
AzureTenantId | Required if IdpType: AzureAd | Azure tenant GUID | Auto-resolves the generic Azure tenant federation metadata URL and default IdP entity ID. |
MetadataUrl | Recommended | HTTPS URL | IdP 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. |
SingleSignOnUrl | Required if no MetadataUrl | HTTPS URL | IdP SSO endpoint for manual config |
IdpEntityId | Required if no MetadataUrl | URI string | IdP entity ID for manual config |
SigningCertificateBase64 | Optional, manual config only | Base64-encoded DER certificate | IdP signing certificate. Not needed when using MetadataUrl. |
EmailClaimType | Optional | URI or attribute name | SAML attribute name for the user's email. Default: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress |
AllowUnsolicitedAuthResponse | Optional | true / false | Allow 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