SAML 2.0
Overview
SAML 2.0 (OASIS SAML 2.0 Core (opens in a new tab)) is a widely deployed XML-based federation standard. LuxID supports SAML 2.0 as an Identity Provider (IdP), allowing Service Providers (SPs) to delegate authentication to LuxID and receive signed XML Assertions in return.
For new integrations, prefer OpenID Connect (OIDC) - it is simpler to implement, uses standard JSON/JWT formats, and carries lower operational overhead. Use SAML when your application or platform requires it.
When to use SAML at LuxID
SAML is the right choice when:
- Your platform only speaks SAML 2.0 and does not support OIDC (e.g., certain ERP or legacy enterprise applications).
- You are federating a Microsoft 365 tenant using WS-Federation or SAML (corporate SSO scenario).
- You are configuring an Atlassian product (Confluence, Jira) via its built-in SAML SSO feature.
- You are integrating ServiceNow using its SAML 2.0 SSO profile.
- Your organisation mandates SAML for compliance or procurement reasons.
For platform-specific step-by-step guides, see Generic SAML setup.
For OIDC-capable platforms, use the OpenID Connect guide instead.
LuxID IdP metadata
The fastest way to configure your SP is to import LuxID's IdP metadata XML. It contains the Entity ID, SSO endpoint URLs, and the IdP's signing certificate.
| Environment | Metadata URL |
|---|---|
| Production | pathname:///saml/luxid_idp_saml_metadata-prod.xml |
| UAT | pathname:///saml/luxid_idp_saml_metadata-uat.xml |
Import the metadata URL directly into your SP's SAML configuration rather than downloading and manually copying values. Many SP libraries and platforms (SimpleSAMLphp, Spring Security SAML2, Okta SP, Azure AD external IdP) can poll a metadata URL and refresh automatically when the IdP rotates its signing certificate.
Key values to note
After importing the metadata, verify the following in your SP configuration:
| Field | Value |
|---|---|
| IdP Entity ID | https://login.luxid.lu/saml/sps/saml20idp-LUXID/saml20 (the entityID attribute in the metadata) |
| SSO Binding | HTTP-Redirect for AuthnRequest; HTTP-POST for Response |
| Assertion signing | Always enabled - LuxID signs every Assertion |
| Signing algorithm | RSA-SHA256 (http://www.w3.org/2001/04/xmldsig-more#rsa-sha256) |
| Digest algorithm | SHA-256 (http://www.w3.org/2001/04/xmlenc#sha256) |
SP registration
Before LuxID can accept AuthnRequests from your SP, you must register the SP during the LuxID Partner onboarding process. You will need to provide:
- Your SP's Entity ID (a URI that uniquely identifies your SP).
- Your SP's Assertion Consumer Service (ACS) URL - the endpoint that receives the SAML Response via HTTP-POST.
- Your SP's metadata XML or, at minimum, the values above.
- The NameID format you require (persistent is the default and recommended).
- Whether you require encrypted Assertions (optional; requires your SP's encryption certificate).
Contact your LuxID Partner integration team to initiate registration. Self-service SP registration is not currently available.
SP-initiated SSO flow
The standard integration pattern is SP-initiated SSO: the user arrives at your application, your SP constructs an AuthnRequest, and redirects the user to LuxID. LuxID authenticates the user and POSTs a signed Response to your ACS URL.
LuxID also supports IdP-initiated SSO (LuxID sends a Response without a prior AuthnRequest), but SP-initiated is strongly preferred - it is more secure and the RelayState ensures the user lands on the correct page after login.
AuthnRequest
Your SP constructs an AuthnRequest and encodes it for the HTTP-Redirect binding: deflate-compress the XML, Base64-encode it, and URL-encode it as the SAMLRequest query parameter.
Example AuthnRequest XML
<?xml version="1.0" encoding="UTF-8"?>
<samlp:AuthnRequest
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
Version="2.0"
IssueInstant="2026-05-22T10:00:00Z"
Destination="https://login.luxid.lu/saml/sps/saml20idp-LUXID/saml20/login"
AssertionConsumerServiceURL="https://app.example.lu/saml/acs"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">
<saml:Issuer>https://app.example.lu/saml/metadata</saml:Issuer>
<samlp:NameIDPolicy
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
AllowCreate="true"/>
<samlp:RequestedAuthnContext Comparison="minimum">
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
Key AuthnRequest fields
| Field | Notes |
|---|---|
ID | Must be unique per request. Prefix with a letter or underscore (XML ID type). |
IssueInstant | UTC timestamp in ISO 8601 format. |
Destination | LuxID's SSO endpoint URL - copy from metadata, do not hard-code. |
AssertionConsumerServiceURL | Your ACS endpoint. Must match what you registered with LuxID. |
Issuer | Your SP's Entity ID. Must match your registration exactly. |
NameIDPolicy Format | Use persistent (recommended). See NameID formats. |
RelayState | Passed as a separate query parameter (not inside the XML). Use it to carry the original URL the user was trying to reach. Keep it under 80 bytes and treat it as untrusted input on return. |
Requesting an assurance level
Use RequestedAuthnContext to specify the minimum assurance level you require. LuxID maps these to its own assurance levels - see Assurance Levels for the full mapping.
| AuthnContextClassRef | LuxID Assurance Level | Typical credential |
|---|---|---|
urn:oasis:names:tc:SAML:2.0:ac:classes:Password | Low | Password only |
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport | Low | Password over HTTPS (single factor) |
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract | Substantial | Password + second factor (OTP, TOTP, or passkey) |
High assurance is reached only with LuxTrust (auth_level 9). Request it via the AuthnContextClassRef value agreed with LuxID for your SP registration; the exact accepted values are defined per Partner at integration time.
If you omit RequestedAuthnContext, LuxID applies its default assurance level for the SP registration.
Signing the AuthnRequest
Signing the AuthnRequest is optional at LuxID but recommended for high-assurance integrations. If you sign, use RSA-SHA256 and include your SP's signing certificate in your SP metadata.
SAML response and assertion
LuxID POSTs a signed SAML Response to your ACS URL. The SAMLResponse parameter contains a Base64-encoded XML document. The structure is:
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_response_id"
InResponseTo="_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
Version="2.0"
IssueInstant="2026-05-22T10:00:30Z"
Destination="https://app.example.lu/saml/acs">
<saml:Issuer>https://login.luxid.lu/saml/sps/saml20idp-LUXID/saml20</saml:Issuer>
<!-- Signature over the entire Response element -->
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- ... omitted for brevity ... -->
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_assertion_id"
Version="2.0"
IssueInstant="2026-05-22T10:00:30Z">
<saml:Issuer>https://login.luxid.lu/saml/sps/saml20idp-LUXID/saml20</saml:Issuer>
<!-- Signature over the Assertion element (always present) -->
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<!-- ... omitted for brevity ... -->
</ds:Signature>
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
SPNameQualifier="https://app.example.lu/saml/metadata">
a7f3c2e1b4d8f0e9c6a5b2d3e4f1a0b7
</saml:NameID>
<saml:SubjectConfirmation
Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
NotOnOrAfter="2026-05-22T10:05:30Z"
Recipient="https://app.example.lu/saml/acs"
InResponseTo="_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
NotBefore="2026-05-22T09:55:30Z"
NotOnOrAfter="2026-05-22T10:05:30Z">
<saml:AudienceRestriction>
<saml:Audience>https://app.example.lu/saml/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="2026-05-22T10:00:28Z"
SessionIndex="_session_id_opaque">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="given_name"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue>Marie</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="family_name"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue>Dupont</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="email"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue>marie.dupont@example.lu</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
Assertion validation
Your SP must validate every incoming Assertion before establishing a session. Skip any of these steps and you expose your application to Assertion replay or forgery attacks.
Verify the signature
LuxID signs every Assertion with RS256 using the certificate published in its IdP metadata. Verify the ds:Signature element on the saml:Assertion using the IdP's public key. Reject any Assertion that fails signature verification.
If the Response element itself is also signed, verify that signature too. Accept either: Assertion-only signing (LuxID's default) or Response + Assertion signing.
Validate conditions
| Element | Validation |
|---|---|
Conditions/@NotBefore | Current time must be greater than or equal to NotBefore (keep the allowed clock skew as small as possible) |
Conditions/@NotOnOrAfter | Current time must be strictly less than NotOnOrAfter (keep the allowed clock skew as small as possible) |
AudienceRestriction/Audience | Must include your SP's Entity ID exactly |
Validate subject confirmation
| Element | Validation |
|---|---|
SubjectConfirmation/@Method | Must be urn:oasis:names:tc:SAML:2.0:cm:bearer |
SubjectConfirmationData/@NotOnOrAfter | Current time must be strictly less than this value |
SubjectConfirmationData/@Recipient | Must match your ACS URL exactly |
SubjectConfirmationData/@InResponseTo | Must match the ID of the AuthnRequest you sent (for SP-initiated SSO) |
Validate the issuer
saml:Issuer must equal the LuxID Entity ID from the metadata. Reject Assertions from unknown issuers.
Prevent replay
Store the Assertion ID and reject any future Assertion with the same ID within the validity window (NotOnOrAfter). Most SAML libraries implement this automatically via an in-memory or Redis-backed store.
Clock skew
Keep the allowed clock skew as small as possible (about a minute or less) in both directions when evaluating NotBefore and NotOnOrAfter. Ensure your server's clock is synchronised via NTP.
NameID formats
The NameID is the primary identifier LuxID uses to identify the authenticated user to your SP.
| Format URI | Description | When to use |
|---|---|---|
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent | Opaque, stable, per-SP identifier. The SAML analogue of the OIDC sub claim. The same user always receives the same NameID at your SP, but different SPs receive different values. | Default and recommended for all integrations |
urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress | The user's LuxID email address. Human-readable, but changes if the user updates their email. | Only when your SP cannot use an opaque identifier |
urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified | LuxID chooses the format. Not recommended - unpredictable. | Avoid |
Request your preferred format in NameIDPolicy in the AuthnRequest. Use persistent unless you have a specific reason to request emailAddress.
Attribute mapping
LuxID includes user attributes in the AttributeStatement of the Assertion. The following table maps LuxID's SAML attribute names to the equivalent OIDC claim and OID (when available):
| SAML Attribute Name | OIDC Claim equivalent | OID (X.500 / LDAP) | Notes |
|---|---|---|---|
given_name | given_name | urn:oid:2.5.4.42 | User's first name |
family_name | family_name | urn:oid:2.5.4.4 | User's family name |
email | email | urn:oid:0.9.2342.19200300.100.1.3 | Primary email address (the LuxID identifier) |
emailVerified | email_verified | - | true if email has been verified |
phoneNumber | phone_number | urn:oid:0.9.2342.19200300.100.1.41 | Present only if the phone scope equivalent is configured |
Attributes are released according to the SP's attribute release policy, configured during Partner onboarding. If an attribute you need is missing from the Assertion, contact your LuxID Partner integration team.
Encrypted assertions
LuxID can encrypt the saml:Assertion element using your SP's public key, so that only your SP can read the user's attributes. Encryption is optional but recommended for integrations handling sensitive personal data.
To enable encrypted Assertions:
- Generate an RSA key pair for your SP (2048-bit minimum; 4096-bit recommended).
- Include the public certificate in your SP's metadata under
KeyDescriptor use="encryption". - Provide your SP metadata to LuxID during registration or update.
- LuxID will encrypt the Assertion using the
http://www.w3.org/2001/04/xmlenc#aes256-cbcalgorithm with an RSA-OAEP encrypted session key.
Your SP library must support XML Encryption (XMLEnc) to decrypt the Assertion. All libraries listed in Recommended SP libraries support encrypted Assertions.
Logout
LuxID does not support SAML Single Logout (SLO). There is no SingleLogoutService in the LuxID IdP metadata, by design: LuxID is a shared Identity Provider used by many Service Providers at once, so a logout initiated by one SP must not terminate the user's session at every other SP. (This is the same principle as the deliberately absent OIDC end_session_endpoint.)
Handle logout locally instead:
- When the user signs out, destroy your own SP session immediately. Do not depend on LuxID to confirm anything.
- If the user explicitly wants to end their LuxID session as well, that is a user-driven action on LuxID, not something your SP triggers. See Session management and Single sign-on for the user-driven logout pattern.
Recommended SP libraries
Use a well-maintained, audited SAML library rather than implementing the protocol yourself. SAML XML parsing is a common source of vulnerabilities (XXE injection, signature wrapping attacks).
| Language / Platform | Library | Notes |
|---|---|---|
| Python | python3-saml (opens in a new tab) (OneLogin / SAML Toolkits) | Widely used; supports deflate binding, signed requests, encrypted Assertions |
| Ruby | ruby-saml (opens in a new tab) (OneLogin / SAML Toolkits) | Companion to python3-saml; active maintenance |
| PHP | SimpleSAMLphp (opens in a new tab) | Full IdP + SP stack; large community; suitable for WordPress and Drupal |
| Java / Spring | Spring Security SAML2 (opens in a new tab) | Native Spring integration; supports OpenSAML 4 |
| .NET | Microsoft.IdentityModel.Tokens.Saml2 (opens in a new tab) | Part of the Wilson library; integrates with ASP.NET Identity |
| Node.js | samlify (opens in a new tab) | TypeScript-native; good for Express and Next.js backends |
Security note
Ensure you are using a current, patched version of your chosen library. Historical SAML vulnerabilities (e.g., XML signature wrapping, comment injection in NameID) affect older versions. Subscribe to security advisories for your library.
Common errors and troubleshooting
| Error | Likely cause | Resolution |
|---|---|---|
urn:oasis:names:tc:SAML:2.0:status:Responder | LuxID could not process the request | Check that your SP Entity ID is registered; contact LuxID if it persists |
urn:oasis:names:tc:SAML:2.0:status:RequestDenied | SP not registered or ACS URL mismatch | Verify your ACS URL and Entity ID match the registration exactly |
| Signature validation failed | Wrong certificate, or metadata not refreshed after key rotation | Re-import IdP metadata; check that you are using the correct environment (Production vs UAT) |
NotOnOrAfter in the past | Clock skew between your server and LuxID | Sync your server clock via NTP; keep the allowed clock skew as small as possible (about a minute or less) in your validation logic |
InResponseTo mismatch | Stale AuthnRequest ID, or replay of an old Response | Ensure you store the ID of the AuthnRequest in session and compare on return |
AudienceRestriction failure | SP Entity ID in your code does not match what LuxID was given | Check for trailing slashes, HTTP vs HTTPS, or other subtle differences |
| Missing attributes | Attribute not in the SP's release policy | Contact LuxID Partner integration team to update your attribute release policy |
UAT environment
During development and testing, point your SP at the UAT metadata:
pathname:///saml/luxid_idp_saml_metadata-uat.xml
The UAT environment uses a different signing certificate. Do not mix UAT and Production metadata. Maintain separate SP registrations for each environment.
The UAT login endpoint replaces login.luxid.lu with login-uat.luxid.lu in all URLs. Verify this from the UAT metadata before configuring your SP.
Summary
- Import LuxID IdP metadata rather than configuring endpoints and certificates manually.
- Use HTTP-Redirect binding for AuthnRequest and HTTP-POST binding for Response.
- Always validate: signature,
Conditions,SubjectConfirmation,Issuer,AudienceRestriction, andInResponseTo. - Use
persistentNameID format as the stable user identifier (equivalent to OIDCsub). - Use a maintained SAML library - do not parse SAML XML yourself.
- Prefer OIDC for new integrations; use SAML only when your platform requires it.
Related documentation
- Generic SAML setup - platform-specific step-by-step guides
- Assurance Levels - mapping AuthnContextClassRef to LuxID assurance levels
- OpenID Connect - the preferred alternative to SAML for new integrations
- OAuth 2.0 for APIs - protecting backend APIs with bearer tokens