Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

Getting into an infinite redirect loop when integrated with Azure AD #219

Closed
tugberkugurlu opened this issue Apr 17, 2015 · 23 comments
Closed

Comments

@tugberkugurlu
Copy link

I am getting into a very interesting state when I try to integrate with Azure AD in an ASP.NET 5 application. I have it working locally but when I deploy to azure web sites, it goes into an infinite redirect loop. It is a single instance Azure Web App and running on DNX 1.0.0-beta4-11532.

I am looking at the azure web app logs and it seem this (roughly, not the whole log):

GET / - 302 0 0 1309 800 16293
POST / - 302 0 0 1240 4950 62
GET / - 302 0 0 1309 912 64
POST / - 302 0 0 1240 4950 15

My guess is that this has something to do with data protection but not sure.

Startup class:

public partial class Startup
{
    private readonly IConfiguration _configuration;
    private readonly IHostingEnvironment _env;

    public Startup(IHostingEnvironment env)
    {
        if (env == null)
        {
            throw new ArgumentNullException("env");
        }

        _env = env;
        _configuration = new Configuration()
            .AddIniFile(@"App_Data/config.ini")
            .AddIniFile(@"App_Data/git.ini")
            .AddEnvironmentVariables("Confidence_");
    }

    // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection();
        services.Configure<AppOptions>(options =>
        {
            options.ServeCdnContent = Convert.ToBoolean(_configuration.Get("App:ServeCdnContent"));
            options.CdnServerBaseUrl = _configuration.Get("App:CdnServerBaseUrl");
            options.GenerateLowercaseUrls = Convert.ToBoolean(_configuration.Get("App:GenerateLowercaseUrls"));
            options.EnableBundlingAndMinification = Convert.ToBoolean(_configuration.Get("App:EnableBundlingAndMinification"));
            options.LatestCommitSha = _configuration.Get("git:sha");
        });

        services.Configure<AppMongoOptions>(options =>
        {
            options.DatabaseName = _configuration.Get("Mongo:AppDatabaseName");
            options.ConnectionString = _configuration.Get("Mongo:AppConnectionString");
        });

        services.Configure<ExternalAuthenticationOptions>(options =>
        {
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        });

        services.AddMvc();
        services.AddCaching();
        services.AddSession();

        IContainer container = CreateAutofacContainer(services);

        return container.Resolve<IServiceProvider>();
    }

    public void Configure(IApplicationBuilder app)
    {
        // Add the following to the request pipeline only in development environment.
        if (string.Equals(_env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase) == false)
        {
            // Add Error handling middleware which catches all application specific errors and
            // send the request to the following path or controller action.
            app.UseErrorHandler("/Home/Error");
        }

        ConfigureAuth(app);

        app.UseStaticFiles();
        app.UseMvc(routes => 
        {
            routes.MapRoute("areaRoute", "{area:exists}/{controller}/{action}");
            routes.MapRoute("defaultRoute", "{controller=Home}/{action=Index}");

            routes.MapRoute(
                "controllerRoute",
                "{controller}",
                new { controller = "Home" });
        });
    }

    private IContainer CreateAutofacContainer(IServiceCollection services)
    {
        var builder = new ContainerBuilder();
        AutofacRegistration.Populate(builder, services);
        builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());

        return builder.Build();
    }
}

public partial class Startup
{
    private static string Authority = string.Empty;
    private static string ClientId = string.Empty;
    private static string AppKey = string.Empty;
    private static string GraphResourceId = string.Empty;

    public void ConfigureAuth(IApplicationBuilder app)
    {
        Authority = string.Format(_configuration.Get("AzureAd:AadInstance"), _configuration.Get("AzureAd:Tenant"));
        ClientId = _configuration.Get("AzureAd:ClientId");
        AppKey = _configuration.Get("AzureAd:AppKey");
        GraphResourceId = _configuration.Get("AzureAd:GraphResourceId");

        // Configure the Session Middleware, Used for Storing Tokens
        app.UseSession();

        // Configure OpenId Connect Authentication Middleware
        app.UseCookieAuthentication(options => 
        {
            options.SessionStore = new MemoryCacheSessionStore();
            options.AutomaticAuthentication = true;
        });

        app.UseOpenIdConnectAuthentication(options =>
        {
            options.AutomaticAuthentication = true;
            options.ClientId = _configuration.Get("AzureAd:ClientId");
            options.Authority = Authority;
            options.PostLogoutRedirectUri = _configuration.Get("AzureAd:PostLogoutRedirectUri");
            options.Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = OnAuthenticationFailed
            };
        });
    }

    private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        notification.HandleResponse();
        notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
        return Task.FromResult(0);
    }
}

project.json file:

{
    "webroot": "wwwroot",
    "version": "1.0.0-*",
    "dependencies": {
        "Confidence.Domain": "",
        "Kestrel": "1.0.0-beta4-*",
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta4-*",
        "Microsoft.AspNet.Mvc": "6.0.0-beta4-*",
        "Microsoft.AspNet.Diagnostics": "1.0.0-beta4-*",
        "Microsoft.AspNet.StaticFiles": "1.0.0-beta4-*",
        "Microsoft.AspNet.Hosting": "1.0.0-beta4-*",
        "Microsoft.AspNet.Server.WebListener": "1.0.0-beta4-*",
        "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta4-*",
        "Microsoft.Framework.Caching.Memory": "1.0.0-beta4-*",
        "Microsoft.AspNet.Session": "1.0.0-beta4-*",
        "Microsoft.AspNet.Authentication.Cookies": "1.0.0-beta4-*",
        "Microsoft.AspNet.Authentication.OpenIdConnect":  "1.0.0-beta4-*",
        "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.1.203031538-alpha"
    },
    "frameworks": {
        "dnx451": {
            "dependencies": {
                "MongoDB.Driver": "2.0.0",
                "Microsoft.Framework.DependencyInjection.Autofac":  "1.0.0-beta4-*"
            }
        }
    },
    "commands": {
        "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5005 --server.urls https://localhost:44300",
        "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5005"
    },
    "publishExclude": [
        "node_modules",
        "bower_components",
        "**.kproj",
        "**.user",
        "**.vspscc"
    ],
    "exclude": [
        "wwwroot",
        "node_modules",
        "bower_components"
    ],
    "scripts": {
        "postrestore": [ "npm install", "bower install" ],
        "prepare": [ "gulp clean", "gulp" ]
    }
}
@brentschmaltz
Copy link
Contributor

We just added some logging that may help. Can you turn on logging and show what is going on?

@tugberkugurlu
Copy link
Author

Does the latest work on the runtime I am on: 1.0.0-beta4-11532?

@brentschmaltz
Copy link
Contributor

It's checked into the dev branch, I can build and run against that.

@tugberkugurlu
Copy link
Author

Cool, thx for this! I will give this a go shorthly.

@tugberkugurlu
Copy link
Author

@brentschmaltz I figured out what was going wrong and didn't have try logging (but logging is always good! 😄)

I was only getting into this redirect loop when I visited the application without HTTPS. I am guessing it has something to do with cookie and its secure flag. I could be mistaking.

I forced HTTPS on my application through an IIS URL Rewrite rule an the problem is solved. I would say this is not a problem on OpenId Connect middleware but could be something that can be documented. Thanks for the help!

@brockallen
Copy link

@tugberkugurlu that's one of the most common reasons for the redirect loop (also with the katana middleware)

@jeffa00
Copy link

jeffa00 commented Dec 15, 2015

I'm late to this party, but thought I'd add for anyone else who happens along that this isn't an ASP 5 issue. The same thing happens with the current shipping version of MVC.

@vetras
Copy link

vetras commented Feb 16, 2017

I had the same problem and my solution was this:

            // (...)
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationScheme = "cookies",
                AutomaticAuthenticate = true,
                CookieName = "AuthenticationCookie",
                // if this is "always" we get a infinite loop when authentication on HTTP (HTTPS is fine)
                CookieSecure = CookieSecurePolicy.SameAsRequest
            });

Thanks @tugberkugurlu you saved my day! I would never have though the problem would be on this line!!

@Tratcher
Copy link
Member

That said, authenticating over HTTP is a terrible idea...

@vetras
Copy link

vetras commented Feb 16, 2017

yes @Tratcher .
I forgot to mention that HTTP is "runs on my machine". I'm still setting up the authentication code.

@pamanes
Copy link

pamanes commented May 10, 2017

I am also having this issue when trying to access an MVC app written in the full .net framework 4.6 on azure websites (XXX.azurewebsites.net) with Azure AD auth. I published a web forms app instead and it works just fine. :(

@kuncevic
Copy link

kuncevic commented Jul 10, 2017

How would you set CookieSecure = CookieSecurePolicy.SameAsReq in case of JWT?

app.UseJwtBearerAuthentication(new JwtBearerOptions()
            {
                Authority = ientityServerUrl,
                Audience = ientityServerUrl,

                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                RequireHttpsMetadata = false,

                TokenValidationParameters = new TokenValidationParameters()
                {
                    ClockSkew = TimeSpan.Zero,
                },
            });

Currently we are not on HTTPS, but we just need to make authentication working from now as it will take for us some time to switch to HTTPS.

as far as I know when you call app.UseIdentityServer() it gets cookies configured automatically, so then sounds like I need to create my custom ConfigureCookieAuthentication() thing, how would it be looks like besides having CookieSecure = CookieSecurePolicy.SameAsReq in it?

UPDATE:
just discovered this one https://github.com/IdentityServer/IdentityServer4/blob/ec17672d27f9bed42f9110d73755170ee9265116/src/IdentityServer4/Hosting/CookieMiddleware.cs#L41 that I get turned in to

private void ConfigureCookieAuthentication(IApplicationBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,

                AutomaticAuthenticate = false,
                AutomaticChallenge = false,
                CookieSecure = CookieSecurePolicy.SameAsRequest
                
            });
        }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
            //--//
	    app.UseIdentity();
            app.UseIdentityServer();

            ConfigureCookieAuthentication(app);
            ConfigureBearerToken(app);
            
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
}

However it is seams like it is didn't help

UPDATE
Turned out that it was some issues in the client code that trigger the redirect so all good now

@Tratcher
Copy link
Member

Tratcher commented Jul 10, 2017

@kuncevic None of this applies to JWT.

[edit]
Your ConfigureCookieAuthentication is adding a duplicate cookie handler. The original is added via UseIdentityServer. I don't see way to override its settings.

@lukastheiler
Copy link

I've mdified my Web.config with this, and all is good now:

<system.webServer>
      <rewrite>
     <rules>
       <rule name="Redirect to https">
         <match url="(.*)"/>
         <conditions>
           <add input="{HTTPS}" pattern="Off"/>
         </conditions>
         <action type="Redirect" url="https://{HTTP_HOST}/{R:1}"/>
       </rule>
     </rules>
   </rewrite>
 </system.webServer>

@RyanTaite
Copy link

@lukastheiler That did it for me! Thank you!

@TheMasterPrawn
Copy link

This problem is infuriating. I am using Visual studio 2017 and have deployed the changes above to an azure website. After deployment everyone can login fine. The next day, visit the same site and the redirect keeps happening. I have checked to ensure the changes were deployed using websitename.csm.azurewebsites.net and the correct config and azure AD settings are present.

@Tratcher
Copy link
Member

It's pretty strange for the behavior to change like that. Can you share a Fiddler trace?

@Daniel-TZS
Copy link

@TheMasterPrawn were you able to resolve your problem? I have a similar problem

@TheMasterPrawn
Copy link

TheMasterPrawn commented Dec 4, 2017

I implemented the web.config as above. I had the same problem, so I implemented the change detailed in http://katanaproject.codeplex.com/wikipage?title=System.Web%20response%20cookie%20integration%20issues&referringTitle=Documentation to manage cookies. The app has been working fine for a month now with no complaints.

I have not tested .NET 5 as suggested as a fix in the article due to time constraints.

@Daniel-TZS
Copy link

Thanks for replying @TheMasterPrawn. My problem turned out to be that I had the authentication cookie set to SameSite = SameSiteMode.Strict, I changed it Lax and all is good. And, my problem occurred with Chrome.

@vatanjoshi
Copy link

##Solved by using Never option for CookieSecureOption##

app.UseCookieAuthentication(new CookieAuthenticationOptions()
            {
                CookieSecure = CookieSecureOption.Never
            })

@Tratcher
Copy link
Member

Tratcher commented May 3, 2018

That's concerning. You've downgraded your security. Were you able to capture a Fiddler trace of the issue?

@TheMasterPrawn
Copy link

TheMasterPrawn commented May 3, 2018 via email

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests