MatiasK
MatiasK

Reputation: 756

Azure AD authentication redirect loop (cookie error) when using two apps

I have two asp.net core web applications (using the same application registration id) protected with Azure AD. If I log in to one, the authentication flow breaks for the other, but not the other way around.

I think it has something to do with the authentication cookies and that the Admin app picks upp the one created for the Portal app. If this assumption is correct, how do I make sure the apps don't use each others cookies?

Setup

Test cases

OK : Load Admin and log in
OK : Load Portal and log in
NOK: Load the Portal and log in, navigate to Admin (auth loop)         
NOK: Load Admin and log in,
        navigate to Portal (credentials not requested, reusing the cookie i guess), 
        reload Admin (auth loop)

The Admin app reports the following error and creates an authentication redirect loop. Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: Cookies was not authenticated. Failure message: Unprotect ticket failed

Startup.cs for the Portal app

public class Startup
{
    public Startup( IConfiguration configuration )
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices( IServiceCollection services )
    {
        services.AddAuthentication( o =>
            {
                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            }
        ).AddOpenIdConnect( o =>
            {
                o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

                o.ClientId = "same-for-both-apps";
                o.CallbackPath = "/portal/signin-oidc";
                o.Authority = "https://login.windows.net/common";
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    RoleClaimType = ClaimTypes.Role,
                    ValidateIssuer = false,
                };
            }
        ).AddCookie( options =>
            { options.AccessDeniedPath = new PathString( "/Account/AccessDenied" ); } );

        services.AddMvc( o =>
            {
                o.Filters.Add( new AuthorizeFilter(
                                    new AuthorizationPolicyBuilder()
                                    .RequireAuthenticatedUser()
                                    .Build() ) );

            }
        ).SetCompatibilityVersion( CompatibilityVersion.Version_2_2 );
    }

    public void Configure( IApplicationBuilder app, IHostingEnvironment env )
    {
        app.MapWhen( IsTargetingPortal, HandlePortalRequest );

        app.Run( async ctx =>
        {
            await ctx.Response.WriteAsync( "Default: info page" );
        } );
    }

    bool IsTargetingPortal( HttpContext ctx )
    {
        return ctx.Request.Path == "/portal/signin-oidc" ||
               ctx.Request.Path == "/portal" ||
               ctx.Request.Host.Host.StartsWith( "portal." );
    }

    void HandlePortalRequest( IApplicationBuilder builder )
    {
        builder.UseAuthentication();
        builder.UseMvc( routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}" );
        } );
    }
}

Startup.cs for the Admin app

public class Startup
{

    public Startup( IConfiguration configuration )
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices( IServiceCollection services )
    {
        services.AddAuthentication( o =>
            {
                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            } 
        ).AddOpenIdConnect( o =>
            {
                o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

                o.ClientId = "same-for-both-apps";                    
                o.Authority = "https://login.windows.net/common";
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    RoleClaimType = ClaimTypes.Role,
                    ValidateIssuer = false,
                };
            } 
        ).AddCookie( options => 
            { options.AccessDeniedPath = new PathString( "/Account/AccessDenied" ); } );

        services.AddMvc( o =>
            {
                o.Filters.Add( new AuthorizeFilter( 
                                    new AuthorizationPolicyBuilder()
                                    .RequireAuthenticatedUser()
                                    .Build() ) );

            } 
        ).SetCompatibilityVersion( CompatibilityVersion.Version_2_2 );
    }

    public void Configure( IApplicationBuilder app, IHostingEnvironment env )
    {
        app.UsePathBase( "/admin" );

        if( env.IsDevelopment() )
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler( "/Error" );
            app.UseHsts();
        }

        app.UseAuthentication();
        app.UseMvc();
    }
}

Upvotes: 1

Views: 832

Answers (2)

MatiasK
MatiasK

Reputation: 756

I solved the cookie mix up by adding

builder.UsePathBase( "/portal" );

to the portal branch of the pipeline

private void HandlePortalRequest( IApplicationBuilder builder )
{            
    builder.UsePathBase( "/portal" ); // the fix!

    builder.UseAuthentication();

    builder.UseMvc( routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Portal}/{action=Index}/{id?}" );
    } );
}

Upvotes: 0

Icculus018
Icculus018

Reputation: 1066

This would be a matter of registering both apps separately in the Azure AD Portal. This will give you a different client ID. Use the different client ID for each app.

enter image description here

Upvotes: 0

Related Questions