Skip to content
This repository has been archived by the owner. 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

Comments

Projects
None yet
@tugberkugurlu
Copy link
Member

commented Apr 17, 2015

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

This comment has been minimized.

Copy link
Contributor

commented Apr 17, 2015

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

@tugberkugurlu

This comment has been minimized.

Copy link
Member Author

commented Apr 17, 2015

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

@brentschmaltz

This comment has been minimized.

Copy link
Contributor

commented Apr 17, 2015

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

@tugberkugurlu

This comment has been minimized.

Copy link
Member Author

commented Apr 17, 2015

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

@tugberkugurlu

This comment has been minimized.

Copy link
Member Author

commented Apr 18, 2015

@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

This comment has been minimized.

Copy link

commented Apr 18, 2015

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

@jeffa00

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Member

commented Feb 16, 2017

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

@vetras

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

Copy link

commented Aug 6, 2017

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

This comment has been minimized.

Copy link

commented Aug 23, 2017

@lukastheiler That did it for me! Thank you!

@TheMasterPrawn

This comment has been minimized.

Copy link

commented Oct 12, 2017

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

This comment has been minimized.

Copy link
Member

commented Oct 12, 2017

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

@Daniel-TZS

This comment has been minimized.

Copy link

commented Dec 3, 2017

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

@TheMasterPrawn

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

commented Dec 4, 2017

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

This comment has been minimized.

Copy link

commented May 3, 2018

##Solved by using Never option for CookieSecureOption##

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

This comment has been minimized.

Copy link
Member

commented May 3, 2018

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

@TheMasterPrawn

This comment has been minimized.

Copy link

commented May 3, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.