End Session Request Validator
Duende.IdentityServer.Validation.EndSessionRequestValidator
Section titled “Duende.IdentityServer.Validation.EndSessionRequestValidator”The built-in validator for RP-Initiated Logout (end session) requests. When a client sends a logout request
with an id_token_hint parameter, this validator checks that the hint matches the currently authenticated
user’s session before proceeding with logout. You can subclass it to customize that matching logic.
ValidateIdTokenHintAsync
Section titled “ValidateIdTokenHintAsync”The main extensibility point is the ValidateIdTokenHintAsync method:
protected virtual Task<EndSessionHintValidationResult> ValidateIdTokenHintAsync( EndSessionHintValidationContext context, CancellationToken ct)Override this method to apply your own logic for deciding whether the id_token_hint in the logout
request matches the current user session. The method receives an EndSessionHintValidationContext
and returns an EndSessionHintValidationResult.
EndSessionHintValidationContext
Section titled “EndSessionHintValidationContext”The context object gives you everything you need to make the validation decision:
-
Subject(ClaimsPrincipal) - the currently authenticated user, taken from the active session. Use this to read the user’s claims, such as their subject ID. -
TokenValidationResult- the result of validating theid_token_hintJWT. This includes all claims from the token (for example,sub,sid,aud) and a reference to the associated client. If the token was invalid, this step would have already failed before your override is called. -
SessionId(string?, may benull) - the current session identifier. This corresponds to thesidclaim in tokens issued for this session. It can benullif the session does not have a session ID.
EndSessionHintValidationResult
Section titled “EndSessionHintValidationResult”Your override must return one of three factory results:
-
EndSessionHintValidationResult.Valid()- the hint matches the current session. Logout proceeds without showing a confirmation prompt to the user (assuming the client has not requested one). -
EndSessionHintValidationResult.Invalid(errorMessage)- the hint does not match the current session. The request is rejected with the provided error message. -
EndSessionHintValidationResult.RequiresConfirmation()- the match is uncertain. Logout proceeds, but IdentityServer setsShowSignoutPrompttotrue, so the user sees a confirmation prompt before being signed out.
Default Behavior
Section titled “Default Behavior”The default implementation uses a sid-first strategy:
- If the
id_token_hintcontains asidclaim and the current session has aSessionId, those two values are compared. If they match, the result isValid(). If they do not match, the result isInvalid(). - If no
sidclaim is present in the token, or the current session has noSessionId, the validator falls back to comparing thesubclaim in the token against the authenticated user’s subject ID. If they match, the result isValid(). If they do not match, the result isInvalid(). - If neither a
sidnor asubclaim is present in the token, the hint is treated as valid andValid()is returned.
Registration
Section titled “Registration”Subclass EndSessionRequestValidator and register your subclass in DI, replacing the built-in registration:
builder.Services.AddTransient<EndSessionRequestValidator, CustomEndSessionRequestValidator>();IdentityServer resolves EndSessionRequestValidator directly, so registering your subclass under the base class type is all you need.
Examples
Section titled “Examples”Always prompt when only sub matches
Section titled “Always prompt when only sub matches”This override tightens the default behavior for the case where the token has no sid claim.
Instead of returning Valid() when only the sub matches, it returns RequiresConfirmation(),
so the user always sees a confirmation prompt in that situation.
public class CustomEndSessionRequestValidator : EndSessionRequestValidator{ protected override Task<EndSessionHintValidationResult> ValidateIdTokenHintAsync( EndSessionHintValidationContext context, CancellationToken ct) { var tokenClaims = context.TokenValidationResult.Claims;
// If the token has a sid claim and the session has a session ID, compare them. var tokenSid = tokenClaims.FirstOrDefault(c => c.Type == "sid")?.Value; if (tokenSid != null && context.SessionId != null) { return Task.FromResult( tokenSid == context.SessionId ? EndSessionHintValidationResult.Valid() : EndSessionHintValidationResult.Invalid("Session ID mismatch")); }
// No sid available - fall back to sub comparison, but require confirmation // instead of silently accepting the match. var tokenSub = tokenClaims.FirstOrDefault(c => c.Type == "sub")?.Value; if (tokenSub != null) { var userSub = context.Subject.FindFirst("sub")?.Value; if (tokenSub != userSub) { return Task.FromResult( EndSessionHintValidationResult.Invalid("Subject mismatch")); }
// Sub matches but no sid - prompt the user to confirm logout. return Task.FromResult(EndSessionHintValidationResult.RequiresConfirmation()); }
// No sub or sid in the token - treat as valid. return Task.FromResult(EndSessionHintValidationResult.Valid()); }}