IdentityServer / IdentityServer4

OpenID Connect and OAuth 2.0 Framework for ASP.NET Core
https://identityserver.io
Apache License 2.0
9.23k stars 4.02k forks source link

Response type is encoded #4423

Closed cosmoKenney closed 4 years ago

cosmoKenney commented 4 years ago

Question

I have a very similar problem to #1379 Except in my case the response type is not double-encoded. What I see in the identity server log is:

_Response type not supported: idtoken+token

My belief is that this is browser version specific. But how can I handle it? Is there a way for me to clean the requests before they make it to IdentityServer middleware?

In #1379 they did some apache config changes to make it work. But I'm hosting both identity server and the client in IIS.

Minimal working example

I'm using the webforms client code almost verbatim from the IdentityServer 3 quickstart samples.

        public void ConfigureAuth(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                AuthenticationType = "Cookies",
                ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToDouble(ConfigurationManager.AppSettings["OWINSessionTimeOutMinutes"]??"90")),
                SlidingExpiration = true
            });

            JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
            var rspt = ConfigurationManager.AppSettings["ResponseType"]; 
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    //AuthenticationType = "oidc",
                    Authority = ConfigurationManager.AppSettings["Authority"],
                    ClientId = ConfigurationManager.AppSettings["ClientId"],
                    RedirectUri = ConfigurationManager.AppSettings["RedirectUri"],
                    PostLogoutRedirectUri = ConfigurationManager.AppSettings["PostLogoutRedirectUri"],
                    ResponseType = ConfigurationManager.AppSettings["ResponseType"], //"id_token token",  for implicit 
                    Scope = ConfigurationManager.AppSettings["Scope"], // "openid profile" for implicit,
                    UseTokenLifetime = false,
                    ClientSecret = ConfigurationManager.AppSettings["ClientSecret"],

                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthenticationFailed = (context) =>
                        {
                            if (
                                context.Exception.Message.StartsWith("OICE_20004")
                                || context.Exception.Message.Contains("IDX10311")
                            )
                            {
                                context.SkipToNextMiddleware();
                                return Task.FromResult(0);
                            }
                            return Task.FromResult(0);
                        },
                        SecurityTokenValidated = async n =>
                        {
                            var claims_to_exclude = new[] { "aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash" };
                            var claims_to_keep = n.AuthenticationTicket.Identity.Claims.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
                            claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));

                            if (n.ProtocolMessage.AccessToken != null)
                            {
                                claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));

                                var userInfoClient = new UserInfoClient(new Uri(ConfigurationManager.AppSettings["UserInfoClientUri"]), n.ProtocolMessage.AccessToken);
                                var userInfoResponse = await userInfoClient.GetAsync();
                                // filter sub since we're already getting it from id_token:
                                var userInfoClaims = userInfoResponse.Claims.Where(x => x.Item1 != "sub").Select(x => new Claim(x.Item1, x.Item2));
                                claims_to_keep.AddRange(userInfoClaims);
                            }

                            var ci = new ClaimsIdentity(
                                n.AuthenticationTicket.Identity.AuthenticationType,
                                JwtClaimTypes.Name,
                                JwtClaimTypes.Role
                            );

                            ci.AddClaims(claims_to_keep);

                            n.AuthenticationTicket = new Microsoft.Owin.Security.AuthenticationTicket(ci, n.AuthenticationTicket.Properties);
                        },
                        RedirectToIdentityProvider = n =>
                        {
                            if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
                            {
                                var id_token = n.OwinContext.Authentication.User.FindFirst("id_token")?.Value;
                                n.ProtocolMessage.IdTokenHint = id_token;
                                if ((n.ProtocolMessage.PostLogoutRedirectUri ?? string.Empty).ToLower().Contains("concurrentsession"))
                                {
                                    n.ProtocolMessage.State = ConfigurationManager.AppSettings["ClientId"] + ":" + n.OwinContext.Authentication.User.GetSubjectId();
                                }
                            }
                            return Task.FromResult(0);
                        }
                    },
                    // SignInAsAuthenticationType = "..." must come last, see here: https://github.com/IdentityServer/IdentityServer3/issues/1176
                    SignInAsAuthenticationType = "Cookies"
                });

            app.UseStageMarker(PipelineStage.Authenticate);
        }

Relevant parts of the log file

fail: Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer[2]
      Connection ID "16501189049181011979", Request ID "8000000c-0003-e500-b63f-84710c7967bb": An unhandled exception was thrown by the application.
System.NotSupportedException: Required context missing.
   at IdentityServer4.Quickstart.UI.AccountController.BuildLoginViewModelAsync(String returnUrl)
   at IdentityServer4.Quickstart.UI.AccountController.Login(String returnUrl) in C:\Git\ProvPubs\Source\ProvPubs.IdentityServer\Quickstart\Account\AccountController.cs:line 73
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()
failfail: IdentityServer4.Validation.AuthorizeRequestValidator[0]
      Response type not supported: id_token+token
{
        "ClientId": "xxx_web",
        "ClientName": "xxx Web Site",
        "RedirectUri": "https://www2.xxx.com/signin-oidc",
        "AllowedRedirectUris": [
          "https://www2.xxx.com/signin-oidc"
        ],
        "SubjectId": "anonymous",
        "RequestedScopes": "",
        "State": "OpenIdConnect.AuthenticationProperties=qRjVDsY9WBs-XRyMX_hq9FjtpgKIWDhtZAcBOdVDM0A60x4jN0GGexVSkHiPRVjWMg8ZJLwAh4g3Pw15W3if-0hOmoWLA-DOgG50LzR8i9Nv6MsrficJC5LcWTeioxtGc1NC69bargjzZbgyxUcKgffHiwmfXnYBuLatPbu9c-boSNiY933kjz1M9nNer5_7GP_hcMXF13sJjVVp2waxhMMrHfLyQOFWTILGoe47uuo",
        "Raw": {
          "client_id": "xxx_web",
          "nonce": "637255683501333386.YWE5ZjgzNGItNDQxYi00OTg1LWFiNmUtMTJiNTgwMWZlNWVmZWVmMzcwMTItM2Q5Ni00MjQ1LTg1MGItNmQ0MjdiOTczMzYw",
          "redirect_uri": "https://www2.xxx.com/signin-oidc",
          "response_mode": "form_post",
          "response_type": "id_token+token",
          "scope": "openid+profile",
          "state": "OpenIdConnect.AuthenticationProperties=qRjVDsY9WBs-XRyMX_hq9FjtpgKIWDhtZAcBOdVDM0A60x4jN0GGexVSkHiPRVjWMg8ZJLwAh4g3Pw15W3if-0hOmoWLA-DOgG50LzR8i9Nv6MsrficJC5LcWTeioxtGc1NC69bargjzZbgyxUcKgffHiwmfXnYBuLatPbu9c-boSNiY933kjz1M9nNer5_7GP_hcMXF13sJjVVp2waxhMMrHfLyQOFWTILGoe47uuo"
        }
      }
cosmoKenney commented 4 years ago

@leastprivilege since this is related to #1379 can you tell me if there is a work around for IIS that would do the same thing the OP on that thread did in Apache?

leastprivilege commented 4 years ago

I personally never ever had this problem.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Questions are community supported only and the authors/maintainers may or may not have time to reply. If you or your company would like commercial support, please see here for more information.

cosmoKenney commented 4 years ago

@leastprivilege I asked in my original post above: Is there a way for me to clean the requests before they make it to IdentityServer middleware? Can you at least answer that? Just because you haven't never ever had this problem doesn't mean it doesn't exist. It is clearly something that comes up. Granted it is not frequent. But, in my industry many people run ancient browsers and in my opinion IdentityServer should be able to handle their quirks. And when IdentityServer fails to do so, I don't feel like it is acceptable for the authors to push the problem back to the consumer.

brockallen commented 4 years ago

Is there a way for me to clean the requests before they make it to IdentityServer middleware?

Write middleware.

cosmoKenney commented 4 years ago

how would i make sure it runs before identity server picks up the request? and is it even possible to modify the query before passing it along?

cosmoKenney commented 4 years ago

@brockallen @leastprivilege This is clearly a bug. How can I convert this issue to a bug report? Or should I just create a new issue as a bug? IdentityServer should handle the encoding. Period.

TheBeardedLlama commented 4 years ago

@cosmoKenney have a look at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1 they explain how the pipeline works and yes you can guarantee your middleware runs first

cosmoKenney commented 4 years ago

@TheBeardedLlama thanks for the non-dismissive response.

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.