4

I'm using IdentityServer4 and one of my clients is a .NET 4.5 MVC application. Everything seems to be working as expected. However, I have a problem. Taking the following flow:

  1. User logs in and gets redirected to the Client application.
  2. User performs different actions successfully.
  3. The id token expires (after, for example, 5 minutes).
  4. User attempts a POST request (for example, a Logout request).
  5. Since the id token has expired and the Logout action is protected with the [Authorize] attribute, thanks to UseOpenIdConnectAuthentication in the Startup.cs file, it will automatically go to IdentityServer4 to fetch a new id token before executing any actions.
  6. A call to IdentityServer4 is done and the new id token is successfully returned.
  7. Now the Logout action is called with a GET method instead of a POST method, which of course, is not an action in my controller.

How can I ensure once the new id token is received, the original request is performed? (In this case, a POST request). Or, in other words, how can I make sure I can log out using an HttpPost Logout action in the MVC client when the id token has already expired?

Just to clarify, the Logout action is just one example. It could be any other POST action.

I suspect I might be missing something here, probably as a consequence of not having a lot of experience with IdentityServer4.

Here's a cleaned-up version of my code, just in case:

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
    Authority = authenticationAuthority,
    ClientId = clientId,
    ResponseType = "code id_token",
    SignInAsAuthenticationType = "Cookies",
    Scope = "openid company.profile offline_access",

    Notifications = new OpenIdConnectAuthenticationNotifications
    {
        AuthorizationCodeReceived = async n =>
        {
            n.RedirectUri = GetRedirectUriWithCorrectScheme(n.Request.Headers);

            // use the code to get the access and refresh token
            var secret = "mysecret";
            var tokenClient = new TokenClient($"{authenticationAuthority}connect/token", clientId,
                secret);

            var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(
                n.Code, n.RedirectUri);

            if (tokenResponse.IsError)
            {
                throw new Exception(tokenResponse.Error);
            }

            // use the access token to retrieve claims from userinfo
            var userInfoClient = new UserInfoClient($"{authenticationAuthority}connect/userinfo");

            var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

            userInfoResponse.Claims.ToList().ForEach(claim => claims.Add(claim));

            claims.Add(new Claim("access_token", tokenResponse.AccessToken));
            claims.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
            claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));

            n.AuthenticationTicket = new AuthenticationTicket(
                new ClaimsIdentity(claims.Distinct(new ClaimComparer()),
                    n.AuthenticationTicket.Identity.AuthenticationType),
                n.AuthenticationTicket.Properties);
        },

        RedirectToIdentityProvider = n =>
        {
            // if signing out, add the id_token_hint
            if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
            {
                var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");

                if (idTokenHint != null)
                {
                    n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                }
            }

            return Task.FromResult(0);
        }
    }
});
Charmander
  • 276
  • 3
  • 20
  • You utilize code flow to get access token, you also got refresh token to request new access token. If your access token be expire, try to get a new one using refresh token before call logout – Dan Nguyen Jul 30 '18 at 02:39
  • @DanNguyen, unless I'm missing something, my problem, in this case, is with the id token, not the access token. – Charmander Jul 30 '18 at 07:58
  • @Charmander What does the redirect flow look like? Is the MVC app redirecting to the authentication provider (302), which is then getting re-authenticated? And the auth provider is redirecting back to the "logout" url? If that's the case, I'd look at a custom Authorize Attribute that pre-flights your authenticated requests with a refresh of the active tokens when it has expired? – Xenolightning Aug 01 '18 at 04:52
  • @Xenolightning, what you are suggesting is similar to what I've ended up doing, but still, I'm not entirely happy with that approach. The OpenIdConnect middleware in the client is doing that already (i.e. getting the new token if it has expired), so adding another custom Authorize Attribute to do something similar looks to me more like a hack rather than a proper solution. – Charmander Aug 01 '18 at 07:36

0 Answers0