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. We support two methods of notifying service providers when using IdP-initiated logout:
- iFrames
- Iterative/Traditional
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.
As a result, we also offer a method inspired by the OpenID Connect front-channel logout, which uses iFrames, allowing the component to send protocol-compliant logout requests whilst keeping a consistent user experience. Using iframes to make logout requests means that the IdP can send SAML logout requests to all SPs in parallel, preventing the entire SLO chain from falling over due to one unresponsive SP.
Our component uses the iFrames approach by default.
The iterative/traditional approach must be enabled by setting UseIFramesForSlo
to false.
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.
iFrames SLO Approach
To trigger IdP-Initiated SLO, the GetSamlSignOutFrameUrl
method on the SAML interaction service, ISamlInteractionService
, must be called using the logoutId
from the IdentityServer end session request. This will generate a URL that must be opened in an iFrame on your LoggedOut screen, much like for OpenID Connect.
See below for a modified LoggedOut OnGet
method from the Duende IdentityServer QuickStart UI:
public async Task OnGet(string logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interactionService.GetLogoutContextAsync(logoutId);
View = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = LogoutOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = String.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
if (logout != null)
{
var samlLogout = await _samlInteractionService.GetSamlSignOutFrameUrl(logoutId, new SamlLogoutRequest(logout));
View.SamlSignOutIframeUrl = samlLogout;
}
}
This iFrame should then be displayed on your LoggedOut view like so:
@if (Model.View.SamlSignOutIframeUrl != null)
{
<iframe width="0" height="0" class="samlsignout" src="@Model.View.SamlSignOutIframeUrl"></iframe>
}
Iterative/Traditional SLO Approach
By default, our product will use iFrames.
To use the iterative approach, you must set the configuration option UseIFramesForSlo
to false.
builder.Services.AddIdentityServer()
.AddSamlPlugin(options =>
{
// Other configuration code removed for brevity
options.UseIFramesForSlo = false;
});
To trigger IdP-Initiated SLO, the ExecuteIterativeSlo
method on the SAML interaction service, ISamlInteractionService
, must be called using the logoutId
from the IdentityServer 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 initiatd SLO you can use GetLogoutCompletionUrl
as the completion URL to have the SP initiated SLO finish one 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 modified Logout page from the Duende IdentityServer QuickStart UI. Here the completion URL has been set as the LoggedOut page. IdentityServer will perform local sign-out, log out of all service providers iteratively, and finally redirect to the LoggedOut page.
public async Task<IActionResult> OnPost()
{
if (User?.Identity.IsAuthenticated == true)
{
LogoutId ??= await _interaction.CreateLogoutContextAsync();
// delete local authentication cookie
await HttpContext.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
// initiate iterative SAML-Initiated SLO
var completionUrl = Url.Page("/Account/Logout/Loggedout", new { logoutId = LogoutId });
await _samlInteractionService.ExecuteIterativeSlo(HttpContext, LogoutId, completionUrl);
return new EmptyResult();
}
return RedirectToPage("/Account/Logout/LoggedOut", new { logoutId = LogoutId });
}
Example 2 - LoggedOut page
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 LoggedOut page from the Duende IdentityServer QuickStart UI.
Here we inject the iterative SAML SLO process after the iFrames have executed but before returning to PostLogoutRedirectUri
.
public async Task OnGet(string logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interactionService.GetLogoutContextAsync(logoutId);
View = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = LogoutOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = String.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
SignOutIframeUrl = logout?.SignOutIFrameUrl
};
// Configure iterative SAML-Initiated SLO
var sloCompletionUrl = View.PostLogoutRedirectUri;
View.PostLogoutRedirectUri = Url.Page("/account/logout/SamlIterativeSlo", new { logoutId = logoutId, completionUrl = sloCompletionUrl });
View.AutomaticRedirectAfterSignOut = true;
}
}
You can then create the SamlIterativeSlo
page, which executes the iterative SLO process.
Here's a very simple example:
public class SamlIterativeSlo : PageModel
{
private readonly ISamlInteractionService _samlInteractionService;
public SamlIterativeSlo(ISamlInteractionService samlInteractionService)
{
_samlInteractionService = samlInteractionService;
}
public async Task<IActionResult> OnGet(string logoutId, string completionUrl)
{
await _samlInteractionService.ExecuteIterativeSlo(HttpContext, logoutId, completionUrl);
return new EmptyResult();
}
}