Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cant find way to set custom redirects (login, accessDenied etc.) for cookie authentication #1783

Closed
michalTurkiewicz opened this issue Nov 21, 2017 · 10 comments

Comments

Projects
None yet
5 participants
@michalTurkiewicz
Copy link

commented Nov 21, 2017

Hi,
Im using IdentityServer4 (2.0.4) with asp core 2 (2.0.3)

The problem is that I cannot set custom redirects for cookie authentication.
If Im using only AspNetCore.Identity, I can set redirects using cookie options:

services.ConfigureApplicationCookie(options => ...)

As soon as I add IdentityServer to Startup, this settings stop working.

I found a way to set redirects in IdentityServer options:

services.AddIdentityServer(options =>
{
options.UserInteraction.LoginUrl = new PathString("/Application/Account/Login");
}
...

but there is no way to set (for example) accessDenied redirect!

So my question is:
How to overwrite IdentityServer settings regarding cookie authentication redirect paths.

I must add, that this problem occures after changing to core 2, in core 1 I didnt have any problem to overwrite those settings.

My startup.cs:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
const string connectionString = "DefaultConnection";
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString(connectionString)));

        services.AddDbContext<FacilityDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString(connectionString)));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddIdentityServer()
            .AddOperationalStore(options =>
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(connectionString,
                        sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)))
            .AddConfigurationStore(options =>
                options.ConfigureDbContext = builder =>
                    builder.UseSqlServer(connectionString,
                        sqlOptions => sqlOptions.MigrationsAssembly(migrationsAssembly)))
            .AddAspNetIdentity<ApplicationUser>()
            .AddDeveloperSigningCredential();

        services.Configure<IdentityOptions>(options =>
        {
            // Password settings
            options.Password.RequireDigit = true;
            options.Password.RequiredLength = 8;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = true;
            options.Password.RequireLowercase = false;
            options.Password.RequiredUniqueChars = 6;

            // Lockout settings
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
            options.Lockout.MaxFailedAccessAttempts = 10;
            options.Lockout.AllowedForNewUsers = true;

            // User settings
            options.User.RequireUniqueEmail = true;
        });

        services.ConfigureApplicationCookie(options =>
        {
            // Cookie settings
            options.Cookie.HttpOnly = true;
            options.Cookie.Expiration = TimeSpan.FromDays(150);
            options.LoginPath = new PathString("/Application/Account/Login");
            options.LogoutPath = new PathString("/Application/Account/Logout");
            options.AccessDeniedPath = new PathString("/Application/Account/AccessDenied");
            options.SlidingExpiration = true;
        });

        //services.AddAuthorization(options =>
        //{
        //    options.AddPolicy("EmployeesOnly", policy => policy.RequireClaim("EmployeeId"));
        //});

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddSignalR();

        services.AddMvc();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext appContext,
        RoleManager<IdentityRole> roleManager, FacilityDbContext facContext, ILoggerFactory loggerfactory)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseIdentityServer();

        app.UseStaticFiles();

        app.UseSignalR(routes =>
        {
            routes.MapHub<MainHub>("mainHub");
        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{area=Application}/{controller=Home}/{action=Index}/{id?}");

            routes.MapRoute(
                name: "areas",
                template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
            );
        });
    }
@brockallen

This comment has been minimized.

Copy link
Member

commented Nov 22, 2017

@brockallen brockallen added the question label Nov 22, 2017

@michalTurkiewicz

This comment has been minimized.

Copy link
Author

commented Nov 22, 2017

Thank You for response!
Unfortunately there is no way to set accessDenied route using UserInteraction options (or I dont understand something).

I think that I found the reason why IdentityServer overwrites custom Cookie options (set by ConfigureApplicationCookie). (I was also trying to add custom cookie settings using
AddAuthentication().AddCookie(options), but with no luck)

This is what I found:
Method AddIdentityServer calls inner method AddCookieAuthentication -> this adds some
CookieConfigurationOptions to services collection.
I suppose that later on, during Configure method execution, those options call method ConfigureInternalCookie (part of IdentityServer4), which effects in overwriting cookie settings with default IdentityServer4 cookie settings.

I have found 2 ways to resolve this situation:

  1. replace method AddIdentityServer with its submethods called one by one, but comment out method AddCookieAuthentication
  2. Before calling ConfigureApplicationCookie, use this method:
        private static void CleanCookieConfig(IServiceCollection services)
        {
            var cookieOptionsList = services
                .Where(s => s.ServiceType == typeof(IConfigureOptions<CookieAuthenticationOptions>)).ToList();

            var originalCookieOptions = cookieOptionsList.First();

            foreach (var opt in cookieOptionsList)
            {
                services.Remove(opt);
            }

            services.Add(originalCookieOptions);

            var cookiePostConfigOptionsList = services
                .Where(s => s.ServiceType == typeof(IPostConfigureOptions<CookieAuthenticationOptions>)).ToList();

            var originalCookiePostConfigOptions = cookiePostConfigOptionsList.First();

            foreach (var opt in cookiePostConfigOptionsList)
            {
                services.Remove(opt);
            }

            services.Add(originalCookiePostConfigOptions);
        }

So this clean all redundant items (I think functions like AddIdentity, AddAspNetIdentity, AddCookieAuthentication - each adds its own items), and leaves only one instance of cookie option settings in services - the original one, added by services.AddIdentity. This seems to fix the problem - I can set all paths using ConfigureApplicationCookie.

@brockallen

This comment has been minimized.

Copy link
Member

commented Nov 22, 2017

Unfortunately there is no way to set accessDenied route using UserInteraction options (or I dont understand something).

Correct -- we only expose options for endpoints that we need to know about. Access denied is an ASP.NET Core (and specifically a cookie handler) concept.

@brockallen

This comment has been minimized.

Copy link
Member

commented Nov 26, 2017

Closing. If you still have issues, feel free to reopen.

@brockallen brockallen closed this Nov 26, 2017

@mackvatoloco

This comment has been minimized.

Copy link

commented Feb 16, 2018

Anwering the original question for everyone still trying to figure this out.
Try using the same configuration method used in IdentityServer4.

Create a class that implements IConfigureNamedOptions.

internal class ConfigureCookieOptions : IConfigureNamedOptions<CookieAuthenticationOptions>
{
	public ConfigureCookieOptions() { }
	public void Configure(CookieAuthenticationOptions options) { }
	public void Configure(string name, CookieAuthenticationOptions options)
        { 
               options.AccessDeniedPath = "YourAccessDeniedPath"; 
              //Any other options you want to set/override
        }
}

Then add it to services collection in your startup.

services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureCookieOptions>();

This way CookieAuthenticationOptions will be configured using both IdentityServer4 internal configuration (which takes the options exposed options from the IdenentyServerOptions) and this configuration class.
Depending on where you add it to your startup it will either do it before or after IdentityServer4. Just make sure you don't mess with the options used by Identityserver4, configure those the provided way.

Hope this helps.

@Edward-Zhou

This comment has been minimized.

Copy link

commented Sep 26, 2018

@mackvatoloco I made a test with your solution under Asp.Net Core 2.1 and IdentityServer4 2.1, but it fail to work for LoginPath. Does it need any other configuration?

@mackvatoloco

This comment has been minimized.

Copy link

commented Sep 26, 2018

What do you mean by fail?

We are still on IdentityServer2.0 and ASP.NET Core 2.0 and the way to configure works fine.
Looking at IdentityServer2.1 seems they still use that same way:

builder.Services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureInternalCookieOptions>(); builder.Services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureInternalCookieOptions>();

As you can see they also do a PostConfiguration. IPostConfigureOptions are run after all IConfigureOptions.

Anyway, I think the problem here is that you are trying to change the login path. Depending on your configuration they might change that on both IConfigureOptions and IPostConfigureOptions. That is why I mentioned:

Just make sure you don't mess with the options used by Identityserver4, configure those the provided way.

They provide a way in the UserInteractionOptions class. To change it you can do what @michalTurkiewicz mentioned:

services.AddIdentityServer(options => { options.UserInteraction.LoginUrl = new PathString("/Application/Account/Login"); }

Both their IConfigureOptions assign the LoginPath in some way from that options.UserInteraction.LoginUrl.

Hope this helps.

@Jehoel

This comment has been minimized.

Copy link

commented Nov 5, 2018

@mackvatoloco I set a breakpoint in both ConfigureCookieOptions.Configure method overloads and it never gets called. My services.AddSingleton<IConfigureOptions<CookieAuthenticationOptions>, ConfigureCookieOptions>(); calls is after I call AddIdentityServer. Am I missing anything?

@mackvatoloco

This comment has been minimized.

Copy link

commented Nov 5, 2018

@Jehoel IConfigureOptions.Configure methods are called when resolving the Options they are intended for. For CookieAuthenticationOptions this is not done until you actually try to authenticate. So you will not hit that breakpoint during server startup.

@Jehoel

This comment has been minimized.

Copy link

commented Nov 5, 2018

@mackvatoloco Thank you! I thought the startup process was all eager instead of lazy.

I did find out that my IConfigureOptions<CookieAuthenticationOptions> and IConfigureNamedOptions<CookieAuthenticationOptions> callbacks weren't ever invoked but my IPostConfigureOptions<CookieAuthenticationOptions> was invoked - which is good enough for me for now.

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