Skip to main content
Version 0.3Draft

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.

tip

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.

EnvironmentMetadata URL
Productionpathname:///saml/luxid_idp_saml_metadata-prod.xml
UATpathname:///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:

FieldValue
IdP Entity IDhttps://login.luxid.lu/saml/sps/saml20idp-LUXID/saml20 (the entityID attribute in the metadata)
SSO BindingHTTP-Redirect for AuthnRequest; HTTP-POST for Response
Assertion signingAlways enabled - LuxID signs every Assertion
Signing algorithmRSA-SHA256 (http://www.w3.org/2001/04/xmldsig-more#rsa-sha256)
Digest algorithmSHA-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

Example AuthnRequest
<?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

FieldNotes
IDMust be unique per request. Prefix with a letter or underscore (XML ID type).
IssueInstantUTC timestamp in ISO 8601 format.
DestinationLuxID's SSO endpoint URL - copy from metadata, do not hard-code.
AssertionConsumerServiceURLYour ACS endpoint. Must match what you registered with LuxID.
IssuerYour SP's Entity ID. Must match your registration exactly.
NameIDPolicy FormatUse persistent (recommended). See NameID formats.
RelayStatePassed 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.

AuthnContextClassRefLuxID Assurance LevelTypical credential
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordLowPassword only
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportLowPassword over HTTPS (single factor)
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContractSubstantialPassword + 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:

SAML Response (decoded)
<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

danger

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

ElementValidation
Conditions/@NotBeforeCurrent time must be greater than or equal to NotBefore (keep the allowed clock skew as small as possible)
Conditions/@NotOnOrAfterCurrent time must be strictly less than NotOnOrAfter (keep the allowed clock skew as small as possible)
AudienceRestriction/AudienceMust include your SP's Entity ID exactly

Validate subject confirmation

ElementValidation
SubjectConfirmation/@MethodMust be urn:oasis:names:tc:SAML:2.0:cm:bearer
SubjectConfirmationData/@NotOnOrAfterCurrent time must be strictly less than this value
SubjectConfirmationData/@RecipientMust match your ACS URL exactly
SubjectConfirmationData/@InResponseToMust 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 URIDescriptionWhen to use
urn:oasis:names:tc:SAML:2.0:nameid-format:persistentOpaque, 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:emailAddressThe 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:unspecifiedLuxID 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 NameOIDC Claim equivalentOID (X.500 / LDAP)Notes
given_namegiven_nameurn:oid:2.5.4.42User's first name
family_namefamily_nameurn:oid:2.5.4.4User's family name
emailemailurn:oid:0.9.2342.19200300.100.1.3Primary email address (the LuxID identifier)
emailVerifiedemail_verified-true if email has been verified
phoneNumberphone_numberurn:oid:0.9.2342.19200300.100.1.41Present 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:

  1. Generate an RSA key pair for your SP (2048-bit minimum; 4096-bit recommended).
  2. Include the public certificate in your SP's metadata under KeyDescriptor use="encryption".
  3. Provide your SP metadata to LuxID during registration or update.
  4. LuxID will encrypt the Assertion using the http://www.w3.org/2001/04/xmlenc#aes256-cbc algorithm 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:

  1. When the user signs out, destroy your own SP session immediately. Do not depend on LuxID to confirm anything.
  2. 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.

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 / PlatformLibraryNotes
Pythonpython3-saml (opens in a new tab) (OneLogin / SAML Toolkits)Widely used; supports deflate binding, signed requests, encrypted Assertions
Rubyruby-saml (opens in a new tab) (OneLogin / SAML Toolkits)Companion to python3-saml; active maintenance
PHPSimpleSAMLphp (opens in a new tab)Full IdP + SP stack; large community; suitable for WordPress and Drupal
Java / SpringSpring Security SAML2 (opens in a new tab)Native Spring integration; supports OpenSAML 4
.NETMicrosoft.IdentityModel.Tokens.Saml2 (opens in a new tab)Part of the Wilson library; integrates with ASP.NET Identity
Node.jssamlify (opens in a new tab)TypeScript-native; good for Express and Next.js backends

Security note

caution

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

ErrorLikely causeResolution
urn:oasis:names:tc:SAML:2.0:status:ResponderLuxID could not process the requestCheck that your SP Entity ID is registered; contact LuxID if it persists
urn:oasis:names:tc:SAML:2.0:status:RequestDeniedSP not registered or ACS URL mismatchVerify your ACS URL and Entity ID match the registration exactly
Signature validation failedWrong certificate, or metadata not refreshed after key rotationRe-import IdP metadata; check that you are using the correct environment (Production vs UAT)
NotOnOrAfter in the pastClock skew between your server and LuxIDSync your server clock via NTP; keep the allowed clock skew as small as possible (about a minute or less) in your validation logic
InResponseTo mismatchStale AuthnRequest ID, or replay of an old ResponseEnsure you store the ID of the AuthnRequest in session and compare on return
AudienceRestriction failureSP Entity ID in your code does not match what LuxID was givenCheck for trailing slashes, HTTP vs HTTPS, or other subtle differences
Missing attributesAttribute not in the SP's release policyContact 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, and InResponseTo.
  • Use persistent NameID format as the stable user identifier (equivalent to OIDC sub).
  • Use a maintained SAML library - do not parse SAML XML yourself.
  • Prefer OIDC for new integrations; use SAML only when your platform requires it.

Updated 2026-07-03