Reputation: 3157
I am using login.gov within asp.net core application. I have it setup with Identity Server 4 allowing users to log in with Azure AD, Google and Login.gov. I can login/logout with all HOWEVER loging.gov requires a call to the following url https://idp.int.identitysandbox.gov/openid_connect/logout? with query string parameters
id_token_hint post_logout_redirect_uri state
Within the logout process of Identity Server how can I capture these other parameters?
At the current moment, using one of the quickstarts it is executing
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
}
I have hooked up in the startup class for this external provider
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProviderForSignOut = context =>
{
string tokenHint = context.ProtocolMessage.IdTokenHint;
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
//postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
postLogoutUri = request.Scheme + "://" + request.Host + postLogoutUri;
}
}
var logoutUri = $"{appConfig.LoginGov.Domain}/openid_connect/logout?id_token_hint={tokenHint}&post_logout_redirect_uri={postLogoutUri}&state={state}";
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
This executes but I am unable to capture the id_token_hint and state parameters. Ideas?
I figured I would be able to capture in the Logout method and send in as an authentication parameter but I am unable to capture it there either.
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
Upvotes: 4
Views: 6782
Reputation: 2570
Late answer. As mentioned here
options.SaveTokens = true; // .net saves id token in the cookie on sign in
options.Events = new OpenIdConnectEvents {
OnRedirectToIdentityProviderForSignOut = async (c) => { // on sign out we set the id_token_hint
c.ProtocolMessage.IdTokenHint = await c.HttpContext.GetTokenAsync("id_token");
}
};
Upvotes: 0
Reputation: 155
Have the same issue. Moreover it looks like it was different parts in HttpContexts on Login and on Logout. I was able to somehow overcome this with the following settings:
AccountController.cs: On ExternalLoginCallback add:
_signInManager.UpdateExternalAuthenticationTokensAsync(info);
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
_logger.LogTrace("ExternalLoginCallback called");
if (remoteError != null)
{
_logger.LogError($"ExternalLoginCallback remoteError found: {remoteError}");
ModelState.AddModelError(string.Empty, _localizer["ErrorExternalProvider", remoteError]);
return View(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
_logger.LogError($"ExternalLoginCallback no ExternalLoginInfo found");
return RedirectToAction(nameof(Login));
}
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
{
// Update any authentication tokens in database if login succeeded
await _signInManager.UpdateExternalAuthenticationTokensAsync(info); // <----- key point!!
return RedirectToLocal(returnUrl);
}
var email = info.Principal.Identity.Name;
var user = await _userManager.FindByEmailAsync(email);
IdentityResult identityResult = null;
if (user == null)
{
user = new TUser
{
UserName = email,
Email = email
};
identityResult = await _userManager.CreateAsync(user);
}
if (identityResult == null || identityResult.Succeeded)
{
identityResult = await _userManager.AddLoginAsync(user, info);
if (identityResult.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
}
AddErrors(identityResult);
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { UserName = email, Email = email });
}
AccountController.cs: On Logout add next:
var userProperies = await _userManager.FindByIdAsync(User.GetSubjectId()); var id_token = await _userManager.GetAuthenticationTokenAsync(userProperies, User.GetIdentityProvider(), "id_token"); HttpContext.Items.Add("id_token", id_token); // <---- key point
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated == true)
{
await _signInManager.SignOutAsync();
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
if (vm.TriggerExternalSignout)
{
// KEY POINT !!!
// Get id_token from database and put it in Request.Items to get it back in OnRedirectToIdentityProviderForSignOut event and put in in id_token_hint
var userProperies = await _userManager.FindByIdAsync(User.GetSubjectId());
var id_token = await _userManager.GetAuthenticationTokenAsync(userProperies, User.GetIdentityProvider(), "id_token");
HttpContext.Items.Add("id_token", id_token); // <---- key point
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}
ExternalProviderService.cs:
SignOutScheme = provider.AuthenticationSchemeName,
SaveTokens = true,
ResponseType = "code id_token",
...
Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProviderForSignOut = async arg =>
{
// var tokens = await arg.HttpContext.GetTokenAsync("idsrv.external", "id_token"); // doesn't work
// Fill id_token_hint for OpenId Connect logout
arg.ProtocolMessage.IdTokenHint = arg.HttpContext.Items["id_token"].ToString(); // <---- KEY POINT!!!
}
}
Upvotes: 1
Reputation: 408
Refer this documentation: https://identityserver4.readthedocs.io/en/release/endpoints/endsession.html?highlight=id_token_hint
When user logged in, Identity server send the id_token i.e. the id_token_hint. if the id_token_hint is valid, it shows logout confirmation page. if endsession is having correct post_logout_redirect_uri, then it directly logout the user and redirect back to post_logout_redirect_uri with state parameter send in endsession request.
Upvotes: 0