diff --git a/README.md b/README.md index 73d9463..d1d2a87 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,49 @@ module.exports = { cert: String or fs.readFileSync("./your/cert/location", "utf-8"), privateKey: fs.readFileSync("./your/private/key/location", "utf-8"), signingCert: fs.readFileSync("./your/signing/cert/location", "utf-8"), + disableRequestedAuthnContext: true, (optional, see SAML configuration below) + wantAuthnResponseSigned: false, (optional, see SAML configuration below) } */ ] }; ``` + +## SAML configuration + +A SAML provider entry accepts the following fields: + +- `name`: A short identifier for the provider. +- `type`: Set to `saml`. +- `url`: The identity provider single sign-on endpoint that CryptPad redirects users to. +- `issuer`: The service provider entity identifier for this CryptPad instance. +- `cert`: The identity provider signing certificate, as a PEM string or an array of PEM strings. CryptPad uses it to verify the signature on the SAML response. +- `privateKey` and `signingCert`: An optional service provider key pair, used only when the identity provider requires CryptPad to sign its authentication requests. Most deployments omit both. +- `disableRequestedAuthnContext`: Optional. Set to `true` to stop CryptPad from requesting a specific authentication context. When unset, node-saml requests the `PasswordProtectedTransport` context. +- `wantAuthnResponseSigned`: Optional. Set to `false` to accept a signed assertion inside an unsigned response. When unset, node-saml requires the response itself to be signed. + +The certificate value must be valid PEM or base64. When you paste a certificate inline, keep every line flush against the left margin, because leading whitespace from indenting the block to match the surrounding code prevents the certificate from parsing. + +### Microsoft Entra ID + +In the Entra enterprise application, set the Identifier (Entity ID) to the same value as the `issuer` field, for example `https://your-domain/saml/metadata`, and set the Reply URL (Assertion Consumer Service URL) to `https://your-domain/ssoauth`. Download the Base64 signing certificate from the same application and use it as the `cert` value. + +Two Entra defaults commonly need attention. First, node-saml requests the `PasswordProtectedTransport` authentication context with exact matching. Entra rejects the sign-in with error AADSTS75011 when the user completes multi-factor or passwordless authentication, because the method no longer matches the requested context. Setting `disableRequestedAuthnContext: true` allows Entra to apply its own policy. + +Second, Entra signs the assertion but not the response wrapper by default, while node-saml requires a signed response and otherwise returns the `Invalid document signature` error. Set the Entra Signing Option to "Sign SAML response and assertion" to keep the stronger posture, or set `wantAuthnResponseSigned: false` to accept assertion-only signing. The assertion signature stays required in both cases. + +A working Entra provider entry looks like this: + +``` +{ + name: 'entra', + type: 'saml', + url: 'https://login.microsoftonline.com//saml2', + issuer: 'https://your-domain/saml/metadata', + cert: `-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE-----`, + disableRequestedAuthnContext: true, + wantAuthnResponseSigned: false, +} +``` diff --git a/protocols/saml.js b/protocols/saml.js index 02603e1..28677a7 100644 --- a/protocols/saml.js +++ b/protocols/saml.js @@ -12,7 +12,9 @@ module.exports = (SSOUtils) => { issuer: cfg.issuer, idpCert: cfg.cert, privateKey: cfg.privateKey, - publicCert: cfg.signingCert + publicCert: cfg.signingCert, + disableRequestedAuthnContext: cfg.disableRequestedAuthnContext, + wantAuthnResponseSigned: cfg.wantAuthnResponseSigned }); cb(void 0, saml); }; @@ -61,7 +63,7 @@ module.exports = (SSOUtils) => { idpData: {} }); }).catch(err => { - Env.Log.error('ERROR_SAML_CALLBACK', err); + Env.Log.error('ERROR_SAML_CALLBACK', Util.serializeError(err)); return void cb('EINVAL'); }); });