This repository has been archived by the owner. It is now read-only.

Infinite Redirect Loop with MVC Client on IIS/ Works fine with IISExpress #3239

Closed
ahsonbukhari opened this Issue Sep 19, 2016 · 19 comments

Comments

Projects
None yet
9 participants
@ahsonbukhari
Copy link

ahsonbukhari commented Sep 19, 2016

  • I read and understood how to enable logging

Question / Issue

Hi,

I've followed the steps for setting up the IdentityServer and WebClients provided here:

http://identityserver.github.io/Documentation/docsv2/overview/mvcGettingStarted.html

and it all works like a charm as long as the Web Client is running on IISExpress. It goes into an infinite loop as the login screen is submitted [and scopes allowed] for a client running on IIS.

I've been stuck with it for the past few days and have tried out all solutions reported by other people having faced similar issues. None of the following reported solutions has worked for me:

  • Writing up the client application from scratch
  • Adding session_start to the Global.asax
  • Using SystemWebCookieManager class as the cookie manager
  • Setting RequireSSL to false (just to eliminate possibilities)

#294
#2496
http://stackoverflow.com/questions/29780627/identityserver-gets-into-infinite-loop-of-authentication

None of the above solutions have worked for me.

To reiterate, I have set up the simplest Server and Client apps following the steps in the documentation to the letter and I've got them to work perfectly as long as the client remains on IISExpress.

I wrote up a custom authorize attribute to debug the "HandleUnauthorizedRequest" method and as I had expected, the break point inside this method keeps getting hit after the login screen is submitted. The section of the log file below is also repeated once the system goes into the infinite loop. These two things indicate that the server is authenticating the client and credentials but the Authentication is not being passed to the IIS based Client.

I have written up several different client apps but run into the same issue each time. I've been using the standard ASP.NET Web Application template with an Empty application and MVC checkbox checked. One single controller and a single action method with the [Authorize] attribute on it. So no custom configurations to the client whatsoever.

Relevant parts of the log file

w3wp.exe Information: 0 : 2016-09-19 16:19:54.647 +01:00 [Information] Creating Hybrid Flow response.
w3wp.exe Information: 0 : 2016-09-19 16:19:54.647 +01:00 [Information] Creating Implicit Flow response.
2016-09-19 16:19:54.647 +01:00 [Debug] Creating access token
2016-09-19 16:19:54.647 +01:00 [Debug] Creating JWT access token
2016-09-19 16:19:54.653 +01:00 [Debug] Creating identity token
w3wp.exe Information: 0 : 2016-09-19 16:19:54.653 +01:00 [Information] Getting claims for identity token for subject: 1
2016-09-19 16:19:54.653 +01:00 [Debug] Creating JWT identity token
2016-09-19 16:19:54.656 +01:00 [Debug] Adding client "mvc" to client list cookie for subject "1"
w3wp.exe Information: 0 : 2016-09-19 16:19:54.656 +01:00 [Information] End authorize request
w3wp.exe Information: 0 : 2016-09-19 16:19:54.656 +01:00 [Information] Posting to https://localhost/WebClient
2016-09-19 16:19:54.656 +01:00 [Debug] Using DefaultViewService to render authorization response HTML
w3wp.exe Information: 0 : 2016-09-19 16:19:54.791 +01:00 [Information] Start authorize request
w3wp.exe Information: 0 : 2016-09-19 16:19:54.792 +01:00 [Information] Start authorize request protocol validation
w3wp.exe Information: 0 : 2016-09-19 16:19:54.792 +01:00 [Information] "Authorize request validation success"
 "{
  \"ClientId\": \"mvc\",
  \"ClientName\": \"MVC Client\",
  \"RedirectUri\": \"https://localhost/WebClient\",
  \"AllowedRedirectUris\": [
    \"https://localhost/WebClient\"
  ],
  \"SubjectId\": \"1\",
  \"ResponseType\": \"code id_token token\",
  \"ResponseMode\": \"form_post\",
  \"Flow\": \"Hybrid\",
  \"RequestedScopes\": \"openid profile\",
  \"State\": \"OpenIdConnect.AuthenticationProperties=_EU6fMGWFO-4wkLpM_Xx39xQe1O9VENU370TkNjKzAQjNhghfleflRczSgMRdlm60vGpQ4sgKdVnQhJb2AwYIgKsEByMKTTq65FZnj93wOo9NclseBq5SYDcV8biersxY-g6BCjfobqX5F05LZD9rCAuXQFOjZMj6qpCsNkNPfM7zRx1o-ASGpFUM3VjOR__kJoeSLBRrgJ7A5vF00SqhMcSHmmpYGo99IE0Pxjylyc\",
  \"Nonce\": \"636098951947841518.NTA1NzIxMzYtYjAwNC00NjM5LWE1YTItZDBlZTA5Nzg0MjA4MjkyZDJkZDYtZWY2ZS00MTk4LTk2NmEtZjk2NzY2MjhhNTc3\",
  \"SessionId\": \"dcf3565c7af64736afce53327fa20cce\",
  \"Raw\": {
    \"client_id\": \"mvc\",
    \"redirect_uri\": \"https://localhost/WebClient\",
    \"response_mode\": \"form_post\",
    \"response_type\": \"code id_token token\",
    \"scope\": \"openid profile\",
    \"state\": \"OpenIdConnect.AuthenticationProperties=_EU6fMGWFO-4wkLpM_Xx39xQe1O9VENU370TkNjKzAQjNhghfleflRczSgMRdlm60vGpQ4sgKdVnQhJb2AwYIgKsEByMKTTq65FZnj93wOo9NclseBq5SYDcV8biersxY-g6BCjfobqX5F05LZD9rCAuXQFOjZMj6qpCsNkNPfM7zRx1o-ASGpFUM3VjOR__kJoeSLBRrgJ7A5vF00SqhMcSHmmpYGo99IE0Pxjylyc\",
    \"nonce\": \"636098951947841518.NTA1NzIxMzYtYjAwNC00NjM5LWE1YTItZDBlZTA5Nzg0MjA4MjkyZDJkZDYtZWY2ZS00MTk4LTk2NmEtZjk2NzY2MjhhNTc3\"
  }
}"
w3wp.exe Information: 0 : 2016-09-19 16:19:54.792 +01:00 [Information] Creating Hybrid Flow response.
w3wp.exe Information: 0 : 2016-09-19 16:19:54.792 +01:00 [Information] Creating Implicit Flow response.
2016-09-19 16:19:54.792 +01:00 [Debug] Creating access token
2016-09-19 16:19:54.792 +01:00 [Debug] Creating JWT access token
2016-09-19 16:19:54.795 +01:00 [Debug] Creating identity token
w3wp.exe Information: 0 : 2016-09-19 16:19:54.795 +01:00 [Information] Getting claims for identity token for subject: 1
2016-09-19 16:19:54.796 +01:00 [Debug] Creating JWT identity token
2016-09-19 16:19:54.799 +01:00 [Debug] Adding client "mvc" to client list cookie for subject "1"
w3wp.exe Information: 0 : 2016-09-19 16:19:54.799 +01:00 [Information] End authorize request
w3wp.exe Information: 0 : 2016-09-19 16:19:54.799 +01:00 [Information] Posting to https://localhost/WebClient
2016-09-19 16:19:54.799 +01:00 [Debug] Using DefaultViewService to render authorization response HTML

Could you please point me in the right direction as to what else I could try out or if someone has already managed to resolve this issue.

Much appreciated

@ahsonbukhari

This comment has been minimized.

Copy link

ahsonbukhari commented Sep 19, 2016

image

Another thing I've noticed is that the break point in the Notifications section never gets hit when on IIS but it does get hit on IISExpress. Not sure if this is helpful.

@ThisNoName

This comment has been minimized.

Copy link

ThisNoName commented Sep 20, 2016

Something like this in global.asax, I think

        protected void Application_EndRequest()
        {
            if (Context.Response.StatusCode == 302 && Context.User.Identity.IsAuthenticated &&
                Context.Response.Headers["Location"].ToLower()
                .StartsWith(Constants.Authority.ToLower() + "/connect/authorize"))
            {
                Context.Response.Clear();
                Context.Response.StatusCode = 401;
            }
        }
@ahsonbukhari

This comment has been minimized.

Copy link

ahsonbukhari commented Sep 20, 2016

Hi ThisNoName,

Thanks for your suggestion. Unfortunately it hasn't worked for me though. The reason as I've discovered through other means as well is that Context.User.Identity.IsAuthenticated remains "false", despite the fact that the server has authenticated the user.

The client context is not being updated and therefore redirects back to the server which already has a valid session and therefore redirects back to the client, and we have our infinite loop. However, if the same client is running on IISExpress, the context does get updated and Context.User.Identity.IsAuthenticated returns true.

@TimGergen

This comment has been minimized.

Copy link

TimGergen commented Sep 20, 2016

I had a similar issue, and found it was a difference in behavior in the Microsoft.Owin.Security.OpenIdConnect library when running under IIS Express vs IIS. The OpenIdConnectAuthenticationOptions contain a setting for the callbackPath. The documentation states this will be determined from the RedirectURI if not provided, but I have found when running under IIS, this does not get set for me. Specifically providing the callback path in the options resolved the issue for me, as it prevented the redirect back to IdentityServer and allowed my code to process the callback request:


new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions            {            
                Authority = AppSettings.IdentityServerIssuerUri,
                ClientId = "myclient",
                RedirectUri = "https://mydevserver.company.local:8088/IdentityAdmin/callback/",
                ResponseType = "id_token token",
                UseTokenLifetime = false,    
                CallbackPath = new PathString("/callback/"),               
                Scope = Constants.RequiredScopes,
SignInAsAuthenticationType = Constants.AuthType,
 }
@ahsonbukhari

This comment has been minimized.

Copy link

ahsonbukhari commented Sep 20, 2016

And we have lift off!!!

Thanks a mill TimGergen. This appears to have resolved the issue for me. It had been quite frustrating to say the least.

I appreciate the details on the background as well.

Cheers!

@brockallen brockallen closed this Sep 20, 2016

@srirajmadhavan

This comment has been minimized.

Copy link

srirajmadhavan commented Nov 18, 2016

Hi,

Having the same issue - I have the applicaiton hosted under a website in IIS as api. the Client setting looks like:

            `ClientId = "assetmanager",
            Authority = "https://nycnc.nav.us/idp/navigatoridp",
            RedirectUri = "http://localhost:1000/api/home/index/",
            //RedirectUri = "http://localhost:53028/?cid=4845",
            SignInAsAuthenticationType = "Cookies",
            ResponseType = "code id_token token",
            Scope = "openid profile toolsapi",
            CallbackPath = new PathString("/home/"),
            UseTokenLifetime = false,

            Notifications = new OpenIdConnectAuthenticationNotifications()
            {
             ......

`

Wanted to know if the callbackpath that i am trying out is correct or not. Any pointers?Microsoft.Owin.Security.OpenIdConnect version 3.0.1.0 in use.

@TimGergen

This comment has been minimized.

Copy link

TimGergen commented Nov 19, 2016

Hi srirajmadhavan,

So this works in IIS Express but not in IIS?

Remember the CallbackPath is where your application will get a post after successfully authorizing the client. My guess is that after authentication, you may be getting a post to 'http://localhost:1000/api/home/index/' since that is what you are passing in as the redirect URI; but the middleware is listening on '/home/'. These two addresses should align. It is possible that your application is not getting a callback at all, the redirect loop issues is tied to some other issue.

Check your logs and see if you are getting a POST to the address http://localhost:1000/api/home/ after logging into Id server and what the resultant code is.

Tim

@srirajmadhavan

This comment has been minimized.

@TimGergen

This comment has been minimized.

Copy link

TimGergen commented Nov 19, 2016

I don't think the authorize attributes matter for this, because open id connect middleware will receive the request prior to your code.

Try changing your callback path to '/home/index/' since that is where you are posting back to and see if that stops the redirects.

Tim

@srirajmadhavan

This comment has been minimized.

Copy link

srirajmadhavan commented Nov 19, 2016

Still the same redirect. :(

Why I doubt Authorize attribute is because, when i created a custom authorize attribute, I could debug and see that the user identity is not set. The notification breakpoint hits and then the custom authorize attribute. Shouldnt the middleware be setting the User Identity? correct me if i am wrong.

Sriraj

@srirajmadhavan

This comment has been minimized.

Copy link

srirajmadhavan commented Nov 19, 2016

Hi Tim,

Finally, keeping a session start on the global.asax with callback path worked!. Is there a reason for this?

Thanks - Sriraj

@TimGergen

This comment has been minimized.

Copy link

TimGergen commented Nov 19, 2016

So the call to the configuration of the openId connect middleware was in session start? It should not need to be in session start - this would indicate it is being called for every session - and really it should only need to be called on app start. I would call the configuration methods from either the Application_Start method in global.asax, or the owin startup.cs class if your app is using that.

@srirajmadhavan

This comment has been minimized.

Copy link

srirajmadhavan commented Nov 19, 2016

I have the configuration in owin startup.cs only. Global.asax had regular MVC registrations.
Added Session_Start to global.asax. Attached the start up i am using.

Startup.txt

Sriraj.

@TimGergen

This comment has been minimized.

Copy link

TimGergen commented Nov 19, 2016

I am not sure why it is not working in Startup.cs, but is in Session_Start.
However, the CallbackPath property does not appear to be set in the startup.cs
you have linked.

@srirajmadhavan

This comment has been minimized.

Copy link

srirajmadhavan commented Nov 19, 2016

As I mentioned before - with CallbackPath, it does not hit the notifications and goes into redirect loop. Same after adding the session_start also. Hence I removed. Will this be an issue with the way the IDP is setup?

@wlafrance

This comment has been minimized.

Copy link

wlafrance commented Mar 7, 2017

@TimGergen the callback trick worked. I had to convert an old MVC 3 application to support IdentityServer3. I added the Session_Start code in the global and that did not fix it.
In my Startup.cs file I had to add your suggested callback trick. After figuring out the correct path all is well. Thanks...

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "app_jcrl",
Authority = Constants.BaseAddress,
RedirectUri = "https://localhost:44379/home/",
PostLogoutRedirectUri = "https://localhost:44379/",
ResponseType = "code id_token",
Scope = "openid profile read write offline_access",
CallbackPath = new PathString("/home/index/"), **// Critical to prevent infinite loop**
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},

@alastair-mcdonald

This comment has been minimized.

Copy link

alastair-mcdonald commented Jul 3, 2017

I struggled with this for 2 days and finally fixed it with the Kentor.OwinCookieSaver fix, shown below in case it helps someone.

startup.configuration() now starts as follows...

app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = "Cookies"
    //CookieManager = new SystemWebCookieManager(), (didn't fix it)
    //CookieSecure = CookieSecureOption.Never  (didn't fix it)
});

All the other proposed solutions did not work:

@Ben-Pattinson

This comment has been minimized.

Copy link

Ben-Pattinson commented Nov 27, 2017

Another way you can have the infinite loop is if you configure everything else correctly, but you neglect to add the:
app.UseAuthentication();
Just had this happen to me - feeling pretty silly now :)

@pvasek

This comment has been minimized.

Copy link

pvasek commented Jul 26, 2018

Just be careful that If your app runs inside a virtual directory (not in the site root), the CallbackPath must include this part as well. For example my app was running in App1 virtual directory so I had to use:

CallbackPath = new PathString("/App1/callback/"), 

That's because in OpenIdConnectAuthenticationHandler is this check:

            // Allow login to be constrained to a specific path. Need to make this runtime configurable.
            if (Options.CallbackPath.HasValue && Options.CallbackPath != (Request.PathBase + Request.Path))
            {
                return null;
            }
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.