AFract
AFract

Reputation: 9705

AuthorizeAttribute does nothing in ASP.NET Core 3 MVC

I have an ASP.NET Core 3.1 MVC project, with a simple authentication system based on cookies, and a few roles and policies. DI is handled with Autofac (not internal DI system of ASP.NET Core).

I know my user is correctly loaded with proper roles, and calls to internal methods like Context.User.IsInRole("Administrator")) (from views) are working as expected.

However, all my AuthorizeAttribute are not working, depending of the content of my Startup whether they seem to do nothing, or I am always redirected to login page (even if the right requirement is fulfilled).

An annotation like

[Authorize(Policy = "Administrator,Collaborator")]

or a simple

[Authorize(Roles = "Administrator")]

placed on an action method seems to do nothing.

I know that order of calls in startup Configure and ConfigureServices matter a lot, but despite many attempts and a lot of reading of similar questions on SO or somewhere else, I was not able to make it work as expected.

I share below my whole Startup file (sorry for that), in hope somebody will be able to point the correct order (or another kind of fix of course), to make it work for my specific case.

Thanks a lot.

    public class Startup
    {
        public Startup(IConfiguration configuration, IWebHostEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;
        }

        public IConfiguration Configuration { get; }
        public IWebHostEnvironment Environment { get; }

        public void ConfigureServices(IServiceCollection services)
        {   
            services.AddControllersWithViews();
            services.AddRazorPages();
            services.AddOptions();
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.Cookie.HttpOnly = true;
                    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                    options.Cookie.SameSite = SameSiteMode.Strict;
                    options.LoginPath = "/Account/Login";
                    options.SlidingExpiration = true;
                    options.ExpireTimeSpan = new TimeSpan(0, 24, 0, 0);
                    options.AccessDeniedPath = "/Error/RightError";
                });

            services.AddAuthorization(options =>
            {
                options.AddPolicy(SecurityPolicies.AdministratorOnly, policy => 
                    policy.RequireClaim(ClaimTypes.Role, UserRoles.Administrator));
                options.AddPolicy(SecurityPolicies.AdministratorOrCollaborator, policy => 
                    policy.RequireClaim(ClaimTypes.Role, UserRoles.Administrator, UserRoles.Collaborator));
            });

            services.AddSession(options => options.IdleTimeout = TimeSpan.FromHours(4));
            services.AddMvc(options => options.Filters.Add(new AuthorizeFilter()))
            .AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

            services.AddKendo();
        }

        // ConfigureContainer is where you can register things directly
        // with Autofac. This runs after ConfigureServices so the things
        // here will override registrations made in ConfigureServices.
        // Don't build the container; that gets done for you by the factory.
        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new MyFrontModule());
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseExceptionHandler(ProcessError);
            }
            else
            {
                app.UseExceptionHandler("/Error/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSession();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseCookiePolicy();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");

                    endpoints.MapControllers();
                endpoints.MapRazorPages();
            });

            var ci = new CultureInfo("fr-FR")
            {
                NumberFormat = { NumberDecimalSeparator = ".", CurrencyDecimalSeparator = "." }
            };

            app.UseRequestLocalization(new RequestLocalizationOptions
            {
                DefaultRequestCulture = new RequestCulture(ci),
                SupportedCultures = new List<CultureInfo> { ci },
                SupportedUICultures = new List<CultureInfo> { ci }
            });
        }

        private void ProcessError(IApplicationBuilder appError)
        {
            appError.Run(async context =>
            {
                // Not relevant for my question
            });
        }
    }

I also read that Json Serializer may change something, I am using Newtonsoft with DefaultContractResolver (as above), and I am using Telerik UI components.

Thanks a lot for any useful advice !

Upvotes: 1

Views: 2520

Answers (2)

Khosro.Pakmanesh
Khosro.Pakmanesh

Reputation: 71

I resolved this issue by adding .AddRoles<IdentityRole to the identity setting in the startup file.

        services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

Upvotes: 0

AFract
AFract

Reputation: 9705

As explained in question, I have never been able to have Authorize attribute working as expected, so as my needs in right management were pretty simple, as a workaround I've just implemented a very straightforward FilterAttribute to verify rights based on owned roles.

public class RoleRequirementAttribute : TypeFilterAttribute
{
    public RoleRequirementAttribute(params string[] claimValues) 
        : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new []{ claimValues.Select(cv => new Claim(ClaimTypes.Role, cv)) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly IEnumerable<Claim> _claims;

    public ClaimRequirementFilter(IEnumerable<Claim> claims)
    {
        _claims = claims;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim =  context.HttpContext.User.Claims.Any(owned => _claims.Any(required => owned.Type == required.Type && owned.Value == required.Value));

        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}

Upvotes: 2

Related Questions