Reputation: 9705
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
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
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