The Rock Solid Knowledge SAML IdP component supports two SAML Single Logout (SLO) flows:
- SP-initiated SLO where the SP can initiate single logout for the current session in the upstream SAML IdP
- IdP-initiated SLO where logout from the IdP initiates single logout for all parties in the current session
IdP-Initiated SLO causes the SAML IdP to call all logged-in Service Providers and inform them that the session is ending.
The original SAML 2.0 specification details an iterative process where the IdP redirects the user to each Service Provider in turn. Complete control is forwarded to all the target service providers iteratively. This redirect chain approach is slightly old-fashioned, error-prone and not very user-friendly. The entire SLO process will fall over even if a single SP in the chain is unresponsive. The remaining SPs will never know that a failed logout attempt was made, resulting in various orphaned sessions, and the user will be displayed a generic HTTP error with no meaningful details from either the IdP or the SP.
Although the iterative approach has drawbacks, it may become necessary due to the upcoming browser changes, which will cause the iframes to block 3rd party cookies.
For a more high-level overview of SAML SLO, check out our article, The Challenge of Building SAML Single Logout.
To use SLO, you must set the configuration option UseIFramesForSlo
to false.
builder.Services.AddOpenIddict()
.AddSamlPlugin(builder =>
{
// Other configuration code removed for brevity
builder.ConfigureSamlOpenIddictServerOptions(x => x.IdpOptions.UseIFramesForSlo =false);
});
To trigger IdP-Initiated SLO, the ExecuteIterativeSlo
method on the SAML interaction service, ISamlInteractionService
, must be called using the logoutId
from the end session request.
This method initiates the traditional logout process.
The ExecuteIterativeSlo
method also takes a completion URL.
Once the SLO process is completed, the user will be redirected to the specified completion URL.
If you are combining this with SP initiated SLO, you can use GetLogoutCompletionUrl
as the completion URL to have the SP initiated SLO finish once the iterative SLO has completed
You can call ExecuteIterativeSlo
at any point, depending on your logic and preference in the logout flow.
Example 1 - Logout page
The following example shows the SamlLogout method of the AuthorizationController in the OpenIddict sample idP. OpenIddict will perform local sign-out, log out of all service providers iteratively, and finally redirect to the LoggedOut page.
[HttpPost("~/connect/saml/logout")]
public async Task<IActionResult> SamlLogout(string confirm, string logoutId, string requestId)
{
if (logoutId == null) return Redirect("/");
if (requestId == null) return Redirect("/");
string completionUrl = await _samlInteractionService.GetLogoutCompletionUrl(requestId);
if (confirm == "Yes")
{
await _samlInteractionService.ExecuteIterativeSlo(HttpContext, logoutId, completionUrl);
return new EmptyResult();
}
return Redirect(completionUrl);
}
Example 2 - Combining SP and IDP initiated SLO with OIDC and SAML Clients
For a true SLO experience you can combine OIDC and SAML SP and IdP initiated SLO to have all sessions terminated when performing any kind of SLO. For this to work, OpenIddict needs to know how to handle this process when triggered by both an OIDC and SAML client.
If you are logging out of OIDC clients using iFrames on the LoggedOut page, you may wish to perform the iterative SLO at a later stage after notifying OIDC clients.
The following example shows the modified Logout method of the AuthorizationController in the OpenIddict IdP sample.
Here we set the redirect of our OIDC iFrame based SLO to a dedicated SAML SLO page to ensure the SAML SLO takes place after the OIDC SLO has completed. If a SAML SP initiated the SLO request, we'll have a requestId and that needs to be passed along to the next page to retrieve the final completion path. If an OIDC client started the SLO the requestId will be null, but we still want to end any SAML sessions, so the user is still redirected to the SAML SLO page just with a null requestId.
[ActionName(nameof(Logout)), HttpPost("~/connect/logout"), ValidateAntiForgeryToken]
public async Task<IActionResult> LogoutPost(string logoutId, string requestId)
{
// Ask ASP.NET Core Identity to delete the local and external cookies created
// when the user agent is redirected from the external identity provider
// after a successful authentication flow (e.g Google or Facebook).
await _signInManager.SignOutAsync();
//No logoutId or requestId means this is not a SAML logout
if (string.IsNullOrWhiteSpace(logoutId) && string.IsNullOrWhiteSpace(requestId))
{
// Returning a SignOutResult will ask OpenIddict to redirect the user agent
// to the post_logout_redirect_uri specified by the client application or to
// the RedirectUri specified in the authentication properties if none was set.
return SignOut(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties
{
RedirectUri = "/"
});
}
return RedirectToAction(nameof(SamlLogout), new { logoutId, requestId });
}
Once the iterative SLO is completed the user will either be redirected to the OIDC postlogout uri, or if the original client was a SAML client doing SP initiated SLO, the SLO endpoint to complete that request