Search code examples
asp.net-mvcidentityserver3openid-connectkatanaowin-middleware

IdentityServer3: OWIN Katana middleware is throwing "invalid_client" error as it cannot get a token


We are using IdentityServer3 as the identity provider and OWIN Katana middleware to do the handshake based on OpenId Connect. The authentication works fine as we were redirected to identity server and back to the originating website. But the issue of invalid_client appears when I try to retrieve the tokens and get claims in the "OpenIdConnectAuthenticationNotifications".

Please check the code (startup class) below and the attached screenshot.

public sealed class Startup
{   
    public void Configuration(IAppBuilder app)
    {
        string ClientUri = @"https://client.local";
        string IdServBaseUri = @"https://idm.website.com/core";l
        string TokenEndpoint = @"https://idm.website.com/core/connect/token";
        string UserInfoEndpoint = @"https://idm.website.com/core/connect/userinfo";
        string ClientId = @"WebPortalDemo";
        string ClientSecret = @"aG90apW2+DbX1wVnwwLD+eu17g3vPRIg7p1OnzT14TE=";

        //AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";
        JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookies"
        });

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = ClientId,
            Authority = IdServBaseUri,
            RedirectUri = ClientUri,
            PostLogoutRedirectUri = ClientUri,
            ResponseType = "code id_token token",
            Scope = "openid profile roles",
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name",
                RoleClaimType = "role"
            },
            SignInAsAuthenticationType = "Cookies",

            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // use the code to get the access and refresh token
                    var tokenClient = new TokenClient(
                        TokenEndpoint,
                        ClientId,
                        ClientSecret);

                    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(UserInfoEndpoint);

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

                    // create new identity
                    var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                    //id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
                    id.AddClaims(userInfoResponse.Claims);

                    id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                    id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                    id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                    id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                    id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                    n.AuthenticationTicket = new AuthenticationTicket(
                        new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                        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);
                }
            }

        });
    }
}

enter image description here

The client configuration at the IdSvr3 has been specified to use Hybrid Flow and I have checked that the client Id and client secret many times to verify that they are correct.

Here is the client configuration at the server side:

enter image description here


Solution

  • I was able to resolve the issue by looking at the logs generated by identity server. The logs said the client secret is incorrect, when I have checked several times that the secret was exact to what was showing on the identity server. But then I realised that the secret should be the actual text and NOT the hashed one. The modified code that worked is below:

    string ClientId = @"WebPortalDemo";
    //string ClientSecret = @"aG90apW2+DbX1wVnwwLD+eu17g3vPRIg7p1OnzT14TE="; // Incorrect secret, didn't work
    string ClientSecret = @"love"; // Actual text entered as secret, worked
    

    Credit: @rawel