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

MVC 5 Identity Server 4 loop redirect #4880

Closed Toso82 closed 4 years ago

Toso82 commented 4 years ago

I use latest version of Identity Server 4

I have this client on Identity Server (Config.cs):

.....
new Client
                {
                    ClientName = "ClientName",
                    ClientId = "TestId",
                    ClientSecrets = { new Secret("SecretId".ToSha256()) },
                    AllowedGrantTypes = GrantTypes.Code,

                    AllowedScopes = new List<string>
                    {
                        IdentityServer4.IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServer4.IdentityServerConstants.StandardScopes.Profile,
                        IdentityServer4.IdentityServerConstants.StandardScopes.Email,
                        "roles"
                    },

                    RequireConsent = false,

                    RedirectUris = { "https://localhost:44364" },

                    PostLogoutRedirectUris = { "https://localhost:44364" },
                    AlwaysIncludeUserClaimsInIdToken = true,

                    AllowPlainTextPkce = false,
                    RequirePkce = true
                }
 .....

In my MVC Application (Startup.Auth.cs)

public void ConfigureAuth(IAppBuilder app)
        {

            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

           app.UseCookieAuthentication(new CookieAuthenticationOptions
           {
               AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
               LoginPath = new PathString("/Account/Login"),

               Provider = new CookieAuthenticationProvider
               {
                   OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
               },

            });
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
                ClientId = "ClientId",

                Authority = "https://localhost:5000",
                RedirectUri = "https://localhost:44364",
                Scope = "openid profile email",

                //SignInAsAuthenticationType = "cookie",

                RequireHttpsMetadata = false,
                UseTokenLifetime = false,

                RedeemCode = true,
                SaveTokens = true,
                ClientSecret = "SecretId",

                ResponseType = OpenIdConnectResponseType.Code,
                ResponseMode = OpenIdConnectResponseMode.Query,

                Notifications = new OpenIdConnectAuthenticationNotifications {
                    RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication) {
                            // generate code verifier and code challenge
                            var codeVerifier = CryptoRandom.CreateUniqueId(32);

                            string codeChallenge;
                            using (var sha256 = SHA256.Create()) {
                                var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
                                codeChallenge = Base64Url.Encode(challengeBytes);
                            }

                            // set code_challenge parameter on authorization request
                            n.ProtocolMessage.SetParameter("code_challenge", codeChallenge);
                            n.ProtocolMessage.SetParameter("code_challenge_method", "S256");

                            // remember code verifier in cookie (adapted from OWIN nonce cookie)
                            // see: https://github.com/scottbrady91/Blog-Example-Classes/blob/master/AspNetFrameworkPkce/ScottBrady91.BlogExampleCode.AspNetPkce/Startup.cs#L85
                            RememberCodeVerifier(n, codeVerifier);
                        }

                        return Task.CompletedTask;
                    },
                    AuthorizationCodeReceived = n =>
                    {
                        // get code verifier from cookie
                        // see: https://github.com/scottbrady91/Blog-Example-Classes/blob/master/AspNetFrameworkPkce/ScottBrady91.BlogExampleCode.AspNetPkce/Startup.cs#L102
                        var codeVerifier = RetrieveCodeVerifier(n);

                        // attach code_verifier on token request
                        n.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier);

                        return Task.CompletedTask;
                    }
                }

            });

        }
        private void RememberCodeVerifier(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> n, string codeVerifier) {
            var properties = new AuthenticationProperties();
            properties.Dictionary.Add("cv", codeVerifier);
            n.Options.CookieManager.AppendResponseCookie(
                n.OwinContext,
                GetCodeVerifierKey(n.ProtocolMessage.State),
                Convert.ToBase64String(Encoding.UTF8.GetBytes(n.Options.StateDataFormat.Protect(properties))),
                new CookieOptions {
                    SameSite = SameSiteMode.None,
                    HttpOnly = true,
                    Secure = n.Request.IsSecure,
                    Expires = DateTime.UtcNow + n.Options.ProtocolValidator.NonceLifetime
                });
        }

        private string RetrieveCodeVerifier(AuthorizationCodeReceivedNotification n) {
            string key = GetCodeVerifierKey(n.ProtocolMessage.State);

            string codeVerifierCookie = n.Options.CookieManager.GetRequestCookie(n.OwinContext, key);
            if (codeVerifierCookie != null) {
                var cookieOptions = new CookieOptions {
                    SameSite = SameSiteMode.None,
                    HttpOnly = true,
                    Secure = n.Request.IsSecure
                };

                n.Options.CookieManager.DeleteCookie(n.OwinContext, key, cookieOptions);
            }

            var cookieProperties = n.Options.StateDataFormat.Unprotect(Encoding.UTF8.GetString(Convert.FromBase64String(codeVerifierCookie)));
            cookieProperties.Dictionary.TryGetValue("cv", out var codeVerifier);

            return codeVerifier;
        }

        private string GetCodeVerifierKey(string state) {
            using (var hash = SHA256.Create()) {
                return OpenIdConnectAuthenticationDefaults.CookiePrefix + "cv." + Convert.ToBase64String(hash.ComputeHash(Encoding.UTF8.GetBytes(state)));
            }
        }
    }

this is my package.config

  <package id="Microsoft.AspNet.Cors" version="5.2.7" targetFramework="net47" />
  <package id="Microsoft.AspNet.Identity.Core" version="2.2.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.Identity.Core.it" version="2.2.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.Identity.EntityFramework" version="2.2.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.Identity.EntityFramework.it" version="2.2.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.Identity.Owin" version="2.2.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.Identity.Owin.it" version="2.2.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.Mvc" version="5.2.7" targetFramework="net47" />
  <package id="Microsoft.AspNet.Mvc.it" version="5.2.7" targetFramework="net47" />

I have also a action with [Authorize]. The problem after login on Identity server do many redirect loop and not work as you like

Toso82 commented 4 years ago

@leastprivilege Hi maybe wrong something or there is somethig I miss. Can you help me? . I can add also in MVC .net core work well all. I try to find how put external login as default and remove login page open two version.

mirusky commented 4 years ago

I recommend to read Scott Brady's blog, article Help! I’m Stuck in a Redirect Loop!. He talks about how to track the error and the scenario to occur this.

EDIT: As @scottbrady91 said it in his article

So, if IdentityServer validated the request successfully (i.e., you didn’t get an error response such as unauthorized_client or invalid_request), then the fault is not with IdentityServer, it’s within the client application. For whatever reason, it is struggling to use the tokens returned by IdentityServer and is getting upset with us.

mirusky commented 4 years ago

@Toso82 Maybe the cookie monster if you are using MVC the .net framework 4.x, I solved this using the Kentor.OwinCookieSaver middleware before any cookie configuration.

Like:

           app.UseKentorOwinCookieSaver(); // <-- THIS LINE, DO A MIRACLE

           app.UseCookieAuthentication(new CookieAuthenticationOptions
           {
               AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
               LoginPath = new PathString("/Account/Login"),

               Provider = new CookieAuthenticationProvider
               {
                   OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
               },

            });
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
                ClientId = "ClientId",

                Authority = "https://localhost:5000",
                RedirectUri = "https://localhost:44364",
                Scope = "openid profile email",

            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
                ClientId = "ClientId",

                Authority = "https://localhost:5000",
                RedirectUri = "https://localhost:44364",
                Scope = "openid profile email",

                //SignInAsAuthenticationType = "cookie",

                RequireHttpsMetadata = false,
                UseTokenLifetime = false,

                RedeemCode = true,
                SaveTokens = true,
                ClientSecret = "SecretId",

                ResponseType = OpenIdConnectResponseType.Code,
                ResponseMode = OpenIdConnectResponseMode.Query,

                // code here ...

                Notifications = new OpenIdConnectAuthenticationNotifications {

                // code here ... 

                }

            });
Toso82 commented 4 years ago

@mirusky I read @scottbrady91 article. But personaly i'm not so clear how to solve and resolve problem. I can try also Kentor.OwinCookieSaver middleware before any cookie configuration. but first it is old and deprecate and second now give me

Bad Request - Request Too Long HTTP Error 400. The size of the request headers is too long.

I add i have just in web.config:


<system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.7" />
    <httpRuntime targetFramework="4.7" maxRequestLength="200000000" />
    <httpModules>
 </system.web>

<security>
      <requestFiltering>
        <!-- imposta il limite massimo di stream in upload a circa 200 Mb -->
        <requestLimits maxAllowedContentLength="200000000" />
      </requestFiltering>
</security>
mirusky commented 4 years ago

The HTTP Error 400. The size of the request headers is too long. is from browser the cookies looks like to setted many times. Yes, the Kentor.OwinCookieSaver middleware is old and deprecated since in new versions of .net (like .net core) solved this issue but in old versions the problem still there. I strongly recommend to try to use it and see if the problem is solved.

I've this app using identity server with redirect solved with Kentor.OwinCookieSaver middleware. But now i'm stuck in a new problem, the user isn't created after login from identityserver. So you could copy and paste the highlighted lines and test.

EDIT: note i'm using the demo.identityserver.io since it's just a proof of concept app

Toso82 commented 4 years ago

@mirusky I add app.UseKentorOwinCookieSaver(); as you suggest but how say before give HTTP` Error 400. The size of the request headers is too long. So i don't solve.

mirusky commented 4 years ago

@Toso82 Could you provide some IdentityServer Logs, showing the requests ? And what's happening in your browser console, network and application cookies tabs?

EDIT: I remembered another error that could cause it, cookies new police from google chrome

You could try to test from another browser like Edge / Firefox ... Or disable #same-site-by-default-cookies flag in your browser to test.

Toso82 commented 4 years ago

@mirusky Hi i don't have a lot of time but this is a problem i want to solve. I find other solution maybe more recent on use app.UseKentorOwinCookieSaver();. My problem i not so clear all passage for all. In other heand i use this:

app.UseCookieAuthentication(new CookieAuthenticationOptions
 {
  AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
  CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager()
 });
 app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
 {
             ......
                SignInAsAuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, //<--- Add this
              .....
}       

This fix seams to work but as you not create user or associate user to other one. I add also i don't have problem with other external login like google.

yoyos commented 4 years ago

@Toso82 is this something like this you have ?

image

With no error:

image

Trying to figure out what is happening here too :)

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.

Toso82 commented 4 years ago

Hi all sorry if i response just now @yoyos @mirusky. I solve just use

AuthenticationMode = AuthenticationMode.Passive,

My Configuration now it is this:

            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
            app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
            app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
                },
            });

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {

                ClientId = "",

                Authority = "",
                RedirectUri = "",
                Scope = "openid profile email",

                RequireHttpsMetadata = false,
                UseTokenLifetime = false,

                RedeemCode = true,
                SaveTokens = true,
                ClientSecret = "",

                ResponseType = OpenIdConnectResponseType.Code,
                ResponseMode = OpenIdConnectResponseMode.Query,

                AuthenticationMode = AuthenticationMode.Passive,

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = n =>
                    {
                        if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                        {
                            // generate code verifier and code challenge
                            var codeVerifier = CryptoRandom.CreateUniqueId(32);

                            string codeChallenge;
                            using (var sha256 = SHA256.Create())
                            {
                                var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
                                codeChallenge = Base64Url.Encode(challengeBytes);
                            }

                            // set code_challenge parameter on authorization request
                            n.ProtocolMessage.SetParameter("code_challenge", codeChallenge);
                            n.ProtocolMessage.SetParameter("code_challenge_method", "S256");

                            // remember code verifier in cookie (adapted from OWIN nonce cookie)
                            // see: https://github.com/scottbrady91/Blog-Example-Classes/blob/master/AspNetFrameworkPkce/ScottBrady91.BlogExampleCode.AspNetPkce/Startup.cs#L85
                            RememberCodeVerifier(n, codeVerifier);
                        }

                        return Task.CompletedTask;
                    },
                    AuthorizationCodeReceived = n =>
                    {
                        // get code verifier from cookie
                        // see: https://github.com/scottbrady91/Blog-Example-Classes/blob/master/AspNetFrameworkPkce/ScottBrady91.BlogExampleCode.AspNetPkce/Startup.cs#L102
                        var codeVerifier = RetrieveCodeVerifier(n);

                        // attach code_verifier on token request
                        n.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier);

                        return Task.CompletedTask;
                    }
                }

            });

I don't know it is correct but without do notthing stop redirect loop
Now i have other problem i then my logout logout also IdentityServer 4,

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.

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.