Issue
I have simple ASP .NET MVC application.
I’m trying to do authentication and authorization.
For authentication I have setup Azure AD and I’m adding role to the HttpContext.User
using a middleware.
I know that the role is added because on the View if I do conditional rendering with User.IsInRole("Admin")
the UI is correctly rendered. But the controller with Authorize[Roles = "Admin"] redirects me to
http://localhost:5433/MicrosoftIdentity/Account/AccessDenied?ReturnUrl=%2FHome%2FAdd
I even tired Authorize[Policy = "Admin"]
but the result is same.
From services if I remove AddMicrosoftIdentityUI()
then it is redirected to
http://localhost:5433/Account/AccessDenied?ReturnUrl=%2FHome%2FAdd
I don’t know how does the Authorize attributes work. Does it not check User.IsInRole("Admin")
? Please let me know
Also for me the roles are added in UserDetails table and I do have a role as ‘Admin’
Also I could have written a custom attribute and make it work. I just want to know what is the real issue right now.
Code:
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<EmpDBContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("EmployeeDBConnection")));
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd");
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI().AddNewtonsoftJson();
services.AddAuthorization(options =>
{
options.AddPolicy("Admin", policy => policy.RequireRole("Admin"));
options.AddPolicy("Employee", policy => policy.RequireRole("Employee"));
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAddClaimsFromDb(); // CUSTOM MIDDLEWARE TO ADD ROLE TO USER
// CHECKING IF ROLE WAS ADDED
app.Use(async (context, next) =>
{
if (context.User != null && context.User.Identity.IsAuthenticated)
{
bool x = context.User.IsInRole("Admin");
System.Console.WriteLine(x); // RETURNS TRUE
}
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
UseAddClaimsFromDb (AddClaimMiddleware.cs)
public class AddClaimMiddleware
{
private readonly RequestDelegate next;
public AddClaimMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext httpContext, EmpDBContext dBContext)
{
var user = httpContext.User;
// does not perist between request
if(user != null && user.Identity.IsAuthenticated)
{
string email = user.Identity.Name;
UserDetail userDetail = await dBContext.UserDetails.FirstOrDefaultAsync(e => e.EmailId == email);
string role = userDetail?.Role ?? "Employee";
ClaimsIdentity claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
user.AddIdentity(claimsIdentity);
// httpContext.Session.SetString("employeeId", userDetail.EmployeeId.ToString());
}
await next(httpContext);
}
}
Solution
I fixed the above by rearranging the middleware sequence
app.UseAuthentication();
app.UseAddClaimsFromDb();
app.UseAuthorization();
I still don’t know the real reason why it worked. It would be great if someone can update this or add a new answer.
Answered By – Sandeep Ranjan
Answer Checked By – Willingham (BugsFixing Volunteer)