I have read through countless tutorials, articles and questions.
I cannot figure out why [Authorize(Roles = "SuperAdmin")]
or any other role is not working. Everyone gets 403 regardless of the role specified:
//[Authorize(Roles = "SuperAdmin")] - Commented out to debug roles
public async Task<IActionResult> Index()
{
var userID = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByIdAsync(userID);
var roles = await _userManager.GetRolesAsync(user);
return View();
}
When I debug the user, I can see that they have the role:
But they still get a 403 on any controller that authorizes roles.
Here are the relevant bits of my program.cs:
builder.Services.AddIdentity<TMSUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddClaimsPrincipalFactory<MyUserClaimsPrincipalFactory>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddTokenProvider<DataProtectorTokenProvider<TMSUser>>(TokenOptions.DefaultProvider);
builder.Services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = new PathString("/Home/HandleError/401");
options.LoginPath = new PathString("/Home/Portal");
});
builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddControllersWithViews();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddTransient<ITools, Tools>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
builder.Services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
var app = builder.Build();
app.UseStatusCodePagesWithReExecute("/Home/HandleError/{0}");
app.UseHsts();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<TMSContext>();
context.Database.EnsureCreated();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
I have changed my code countless times over the last few months trying to get this to work, but now I'm just begging for help. This is very frustrating! What have I missed? Could something else in my code be preventing this from working properly?
Thank you!
I finally came up with a solution for this. It should not have been this complicated! I've used roles in other versions .net without this problem.
I created a custom Authorize Filter and I'm just hitting the db (which I assume all the helpers do as well so not really a big deal):
Part of this I got from this link
First, I created a model with my roles:
public enum Role
{
User, Admin, SuperAdmin
}
Then a custom Authorize Filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
private readonly IList<Role> _roles;
public AuthorizeAttribute(params Role[] roles)
{
_roles = roles ?? new Role[] { };
}
public void OnAuthorization(AuthorizationFilterContext context)
{
// Skip authorization if action is decorated with [AllowAnonymous] attribute
var allowAnonymous = context.ActionDescriptor.EndpointMetadata
.OfType<AllowAnonymousAttribute>().Any();
if (allowAnonymous)
return;
// Authorization
var user = context.HttpContext.User;
if (user != null && _roles.Any())
{
using (var db = context.HttpContext.RequestServices
.GetService<ApplicationDbContext>())
{
var userRoles = (from ur in db.UserRoles
join r in db.Roles on ur.RoleId equals r.Id
join u in db.Users on ur.UserId equals u.Id
where u.UserName == user.Identity.Name
select new
{
r.Name
}).ToList();
var isAuthorized = false;
foreach (var role in userRoles)
{
Role claimRole;
Enum.TryParse(role.Name, out claimRole);
if (_roles.Contains(claimRole))
isAuthorized = true;
}
if (isAuthorized)
return;
}
}
context.Result = new UnauthorizedResult();
}
}
And I use it like this:
[Authorize(Role.SuperAdmin)]
public async Task<IActionResult> Index()
{
return View();
}
I do like this approach. Its going to allow me to do some other things I wanted to do on a per controller/user level - I just can't believe it was this complicated!
Hope this helps somebody. If there's any glaring issues with this, please let me know. I'm officially balled.