Table of Contents

Class SamlLogoutRequestValidator

Namespace
Fdyay.IdentityServer.Saml.Idp
Assembly
Fdyay.IdentityServer.Saml.dll

What a LogoutRequest must be before the single logout endpoint acts on it. Duende 8.0.1's LogoutRequestValidator, its steps measured and run in its order; until 0.153.0 these checks were the endpoint's own.

public class SamlLogoutRequestValidator : ILogoutRequestValidator
Inheritance
SamlLogoutRequestValidator
Implements
Inherited Members

Remarks

ValidateAsync(ValidatedLogoutRequest, CancellationToken) runs the steps and is not virtual, as Duende's is not. Two rules are kept there, out of an override's reach. A request whose signature does not verify against its provider is not checked further and never ends a session by itself — the endpoint hands it to the logout page for the user to confirm, as it always has, where Duende refuses it — whatever ValidateSignatureTrust(ValidatedLogoutRequest) answers. And a request passes only with a provider found.

Duende checks no IssueInstant age on a LogoutRequest, and neither does this; nor, by default, whether its NameID is this browser's — MatchLogoutRequestToSession turns Duende's session checks on (see ValidateSessionAsync(ValidatedLogoutRequest, CancellationToken)).

Constructors

SamlLogoutRequestValidator(ISamlServiceProviderStore, IOptions<SamlOptions>?, TimeProvider?, IServerUrls?)

public SamlLogoutRequestValidator(ISamlServiceProviderStore serviceProviders, IOptions<SamlOptions>? options = null, TimeProvider? time = null, IServerUrls? urls = null)

Parameters

serviceProviders ISamlServiceProviderStore

The registry the request's issuer is looked up in.

options IOptions<SamlOptions>

The SAML options; the defaults when null.

time TimeProvider

The clock NotOnOrAfter is read against; the system's when null.

urls IServerUrls

Where this server is, which a request's Destination must name; not checked when null.

SamlLogoutRequestValidator(ISamlServiceProviderStore, IOptions<SamlOptions>?, TimeProvider?, IServerUrls?, IUserSession, ILogger<SamlLogoutRequestValidator>?)

The validator with the browser's session, which ValidateSessionAsync(ValidatedLogoutRequest, CancellationToken) matches a request against when MatchLogoutRequestToSession asks it to. The one AddSaml registers, since 0.159.0.

public SamlLogoutRequestValidator(ISamlServiceProviderStore serviceProviders, IOptions<SamlOptions>? options, TimeProvider? time, IServerUrls? urls, IUserSession userSession, ILogger<SamlLogoutRequestValidator>? logger = null)

Parameters

serviceProviders ISamlServiceProviderStore

The registry the request's issuer is looked up in.

options IOptions<SamlOptions>

The SAML options; the defaults when null.

time TimeProvider

The clock NotOnOrAfter is read against; the system's when null.

urls IServerUrls

Where this server is, which a request's Destination must name; not checked when null.

userSession IUserSession

The SAML sessions this browser holds.

logger ILogger<SamlLogoutRequestValidator>

Where a request that matches no session is recorded, as Duende records it; nowhere when null.

Methods

ValidateAsync(ValidatedLogoutRequest, CancellationToken)

Validates request, finding its service provider.

public Task<SamlLogoutRequestValidationResult> ValidateAsync(ValidatedLogoutRequest request, CancellationToken ct)

Parameters

request ValidatedLogoutRequest
ct CancellationToken

Returns

Task<SamlLogoutRequestValidationResult>

ValidateDestination(ValidatedLogoutRequest)

Refuses a request addressed to another server. Duende's, measured.

protected virtual SamlLogoutRequestValidationResult ValidateDestination(ValidatedLogoutRequest request)

Parameters

request ValidatedLogoutRequest

Returns

SamlLogoutRequestValidationResult

Remarks

Its Destination must be this server's single logout URL, compared ignoring case; a signed request must name one. Not checked when the validator was given no IServerUrls to know where it is.

ValidateNotOnOrAfter(ValidatedLogoutRequest)

Refuses a request past its NotOnOrAfter, when it has one, by more than the clock skew. Duende's.

protected virtual SamlLogoutRequestValidationResult ValidateNotOnOrAfter(ValidatedLogoutRequest request)

Parameters

request ValidatedLogoutRequest

Returns

SamlLogoutRequestValidationResult

Remarks

The provider's ClockSkew, else ClockSkew.

ValidateSessionAsync(ValidatedLogoutRequest, CancellationToken)

Whether the session the request names is this browser's. Duende's step, measured; passes by default, and runs Duende's checks when MatchLogoutRequestToSession, else MatchLogoutRequestToSession, says so.

protected virtual Task<SamlLogoutRequestValidationResult> ValidateSessionAsync(ValidatedLogoutRequest request, CancellationToken ct)

Parameters

request ValidatedLogoutRequest
ct CancellationToken

Returns

Task<SamlLogoutRequestValidationResult>

Remarks

With the checks on, as Duende 8.0.1 makes them: nobody signed in (Subject) passes. For a signed-in user, a request with no NameID is refused (name_id_required; Duende's "LogoutRequest must contain a NameID"); one from a provider this browser holds no SAML session with passes with SessionFound false, which the endpoint answers Success without ending the session; one whose NameID matches none of the provider's sessions, compared ordinally, is refused (name_id_mismatch; Duende's "NameID does not match any active session"); and one whose SessionIndex names none of the sessions with that NameID passes with SessionFound false. Each is logged with Duende's message.

The switch is read here, in the step, rather than in ValidateAsync(ValidatedLogoutRequest, CancellationToken), so an override of this step replaces Duende's checks whatever the switch says, as an override of Duende's does. Nothing here needs keeping from an override: the step is reached only by a request whose signature verified, and it can only refuse a request or leave a session running, never end one.

Ours: off by default (decided 2026-10-14) — Duende always runs them — because each changes what this server does with a verified request: it ends the session. With the switch on, a validator built without IUserSession throws rather than pass a request it was asked to match.

ValidateSignatureTrust(ValidatedLogoutRequest)

Refuses a request whose signature does not verify against a provider that signs. Duende's.

protected virtual SamlLogoutRequestValidationResult ValidateSignatureTrust(ValidatedLogoutRequest request)

Parameters

request ValidatedLogoutRequest

Returns

SamlLogoutRequestValidationResult

Remarks

Ours: a provider with no signing certificate passes, and its request goes to the logout page for the user to confirm; Duende refuses every request without a trusted signature ("The LogoutRequest signature is missing or not trusted").

ValidateSpAsync(ValidatedLogoutRequest, CancellationToken)

Finds the service provider the request's issuer names, as ServiceProvider. Duende's.

protected virtual Task<SamlLogoutRequestValidationResult> ValidateSpAsync(ValidatedLogoutRequest request, CancellationToken ct)

Parameters

request ValidatedLogoutRequest
ct CancellationToken

Returns

Task<SamlLogoutRequestValidationResult>

Remarks

Ours: a provider with no single logout endpoint is not refused, as Duende refuses it ("SP does not have any SingleLogoutServiceUrls configured"): its session is ended and the failure to answer it recorded, as before. Nor is Enabled read here — the stores answer a disabled provider as none, as the AuthnRequest validator relies on.

ValidateVersion(ValidatedLogoutRequest)

Refuses a request not written under SAML 2.0. Duende's.

protected virtual SamlLogoutRequestValidationResult ValidateVersion(ValidatedLogoutRequest request)

Parameters

request ValidatedLogoutRequest

Returns

SamlLogoutRequestValidationResult