David
David

Reputation: 3157

Identity Server 4 getting Id_Token_Hint on log out

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

Answers (3)

Diego Frehner
Diego Frehner

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

nzim
nzim

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

Ravi
Ravi

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

Related Questions