feat: per-request ForceAuthn for createLoginRequest (closes #359)#618
Merged
Conversation
- saml-core §3.4.1 — `ForceAuthn` attribute on `<samlp:AuthnRequest>`
(`xs:boolean`, `use="optional"`).
- saml-profiles §4.1.4.1 — Web Browser SSO Profile interpretation
("the identity provider MUST authenticate the presenter directly
rather than rely on a previous security context").
Callers had no first-class way to ask the IdP to re-prompt the user
for credentials on a single login request. ForceAuthn is the
spec-defined boolean for exactly this use case (step-up, re-auth on
sensitive operations) and slots cleanly into the per-request options
bag introduced in #612.
- Extend `CreateLoginRequestOptions` with `forceAuthn?: boolean` so
callers can opt in per request without mutating entity settings.
- Splice `ForceAuthn="{ForceAuthn}"` into the default AuthnRequest
template; the `replaceTagsByValue` omission rule from #614 already
drops the attribute when the value is undefined, so backwards-
compatible callers see no change.
- Plumb `forceAuthn` from `ServiceProvider#createLoginRequest`
through all three login bindings (redirect / post / simpleSign)
via a new optional trailing parameter.
- Default template renders `ForceAuthn="true"` when set and omits
the attribute entirely when undefined.
- Each binding (redirect, post, simpleSign) renders `ForceAuthn="true"`
end-to-end when `{ forceAuthn: true }` is passed.
- Each binding omits the attribute when the options bag (or the
whole 3rd parameter) is absent — locks in the back-compat contract.
- `forceAuthn: false` renders `ForceAuthn="false"` verbatim, which
is still a valid `xs:boolean` per saml-core §3.4.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 2, 2026
tngan
added a commit
that referenced
this pull request
May 14, 2026
closes #437) - saml-core §3.4.1 — `<samlp:AuthnRequest>` schema: `AssertionConsumerServiceIndex`, `AssertionConsumerServiceURL`, and `ProtocolBinding` are all `use="optional"` and mutually exclusive (the spec text: "If the `<AssertionConsumerServiceIndex>` attribute is present, neither `<AssertionConsumerServiceURL>` nor `<ProtocolBinding>` may be set"). - saml-profiles §4.1.4.1 — Web Browser SSO Profile permits the index form; the IdP looks up the endpoint in the SP's metadata. Some IdPs (legacy Shibboleth deployments, certain ADFS configurations) prefer the metadata-indexed ACS form so the SP can declare multiple ACS endpoints once and select one by index per request, rather than echoing the URL+ProtocolBinding pair in every AuthnRequest. samlify previously hardcoded `ProtocolBinding="…HTTP-POST"` and only emitted the URL form, which left those peers unaddressable from this library. - `CreateLoginRequestOptions.assertionConsumerServiceIndex?: number` — per-request opt-in. JSDoc spells out mutual exclusion semantics: when set, the URL and ProtocolBinding are both omitted (index wins). - `defaultLoginRequestTemplate` now uses placeholders for all three attributes; the omission rule from #614 (`replaceTagsByValue` drops attributes whose value is `null`/`undefined`) handles the swap so no new omission logic is added. - `entity-sp.ts:createLoginRequest` plumbs the new option to all three binding builders (`redirect`, `post`, `simpleSign`), mirroring the shape introduced for `forceAuthn` in #618. - Each binding builder accepts `assertionConsumerServiceIndex?: number` and sets `ProtocolBinding` / `AssertionConsumerServiceURL` to `undefined` when the index is supplied; the metadata-derived ACS URL is suppressed in that branch. - New `describe('AssertionConsumerServiceIndex (#437, saml-core §3.4.1)')` block in `test/units.ts` covering: - Default template renders the index attribute and drops URL + ProtocolBinding when the index is set (incl. `index=0`). - Default template renders URL + ProtocolBinding (and drops the index) when the index is undefined. - Byte-identical pin: literal-string compare on the rendered XML for a backwards-compatible legacy call (modulo `ID=` and `IssueInstant=`). - Redirect / post / simpleSign bindings each verified end-to-end on the real default-template path. - Backwards-compat regression for each binding when no options are passed. - `yarn test` (302 / 1 skipped) and `yarn coverage` (≥90 across all metrics) pass; `npx tsc` is clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tngan
added a commit
that referenced
this pull request
May 14, 2026
closes #437) (#623) - saml-core §3.4.1 — `<samlp:AuthnRequest>` schema: `AssertionConsumerServiceIndex`, `AssertionConsumerServiceURL`, and `ProtocolBinding` are all `use="optional"` and mutually exclusive (the spec text: "If the `<AssertionConsumerServiceIndex>` attribute is present, neither `<AssertionConsumerServiceURL>` nor `<ProtocolBinding>` may be set"). - saml-profiles §4.1.4.1 — Web Browser SSO Profile permits the index form; the IdP looks up the endpoint in the SP's metadata. Some IdPs (legacy Shibboleth deployments, certain ADFS configurations) prefer the metadata-indexed ACS form so the SP can declare multiple ACS endpoints once and select one by index per request, rather than echoing the URL+ProtocolBinding pair in every AuthnRequest. samlify previously hardcoded `ProtocolBinding="…HTTP-POST"` and only emitted the URL form, which left those peers unaddressable from this library. - `CreateLoginRequestOptions.assertionConsumerServiceIndex?: number` — per-request opt-in. JSDoc spells out mutual exclusion semantics: when set, the URL and ProtocolBinding are both omitted (index wins). - `defaultLoginRequestTemplate` now uses placeholders for all three attributes; the omission rule from #614 (`replaceTagsByValue` drops attributes whose value is `null`/`undefined`) handles the swap so no new omission logic is added. - `entity-sp.ts:createLoginRequest` plumbs the new option to all three binding builders (`redirect`, `post`, `simpleSign`), mirroring the shape introduced for `forceAuthn` in #618. - Each binding builder accepts `assertionConsumerServiceIndex?: number` and sets `ProtocolBinding` / `AssertionConsumerServiceURL` to `undefined` when the index is supplied; the metadata-derived ACS URL is suppressed in that branch. - New `describe('AssertionConsumerServiceIndex (#437, saml-core §3.4.1)')` block in `test/units.ts` covering: - Default template renders the index attribute and drops URL + ProtocolBinding when the index is set (incl. `index=0`). - Default template renders URL + ProtocolBinding (and drops the index) when the index is undefined. - Byte-identical pin: literal-string compare on the rendered XML for a backwards-compatible legacy call (modulo `ID=` and `IssueInstant=`). - Redirect / post / simpleSign bindings each verified end-to-end on the real default-template path. - Backwards-compat regression for each binding when no options are passed. - `yarn test` (302 / 1 skipped) and `yarn coverage` (≥90 across all metrics) pass; `npx tsc` is clean. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
forceAuthn?: booleantoCreateLoginRequestOptionsso callers can request that the IdP re-authenticate the presenter on a single login request, without mutating entity settings.ForceAuthn="{ForceAuthn}"into the default<samlp:AuthnRequest>template; thereplaceTagsByValueomission rule from fix: omit AuthnRequest attributes whose value is null/undefined (closes #455) #614 drops the attribute when the value is undefined, so existing callers see no behaviour change.forceAuthnfromServiceProvider#createLoginRequestthrough all three login bindings (redirect / post / simpleSign).Closes #359.
Spec reference
saml-core §3.4.1—ForceAuthnattribute on<samlp:AuthnRequest>(xs:boolean,use="optional").saml-profiles §4.1.4.1— Web Browser SSO Profile interpretation: "the identity provider MUST authenticate the presenter directly rather than rely on a previous security context".Test plan
yarn test— all existing + new tests pass (273 / 1 skipped).yarn coverage— all four global thresholds (statements / branches / functions / lines) hold at >= 90%.npx tsc --noEmit— clean.ForceAuthn="true"when set.ForceAuthn="true"after deflate + base64 round-trip.ForceAuthn="true"after base64 decode.ForceAuthn="true"after base64 decode.ForceAuthn=entirely when no options bag is supplied (back-compat).forceAuthn: falserendersForceAuthn="false"verbatim — still a validxs:boolean.🤖 Generated with Claude Code