Windows authentication and authorization in ASP.NET Core MVC C# based on roles from database

I tried to develop an ASP.NET Core MVC app with Windows authentication, and authorization based on my own tables in the database.
After a lot of hours struggling with authentication and authorization stuff on the net, you guys helped me (Read more) to find an easy solution.
Answer
Add Authentication and Authorization (Claims based) to Web Application in VS 2022 as "ASP.net core MVC"
1- database tables
2- program.cs
3- claimtransformation class
4- controller example
Create application can be done with VS Code too. MVC, is an architecture based on Model-Controller-Views and easy to work with it. the business logic is placed in Controllers.
1. Database tables
I created the database in SQL Server, and perform the connection with "Entity Framework Core" package.
The tables that relevant for this subject are Roles, UserRoles and Employees
the entities models that retrieve data from database and store in Models
folder:
Role.cs
[Table("Tbl_Roles")]
public class Role
{
[Key]
public int Id { get; set; }
[StringLength(50)]
public string Name { get; set; }
public virtual List<UserRole> UserRoles { get; set; } //foreign key to UserRoles table
}
UserRole.cs
[Table("Tbl_UserRoles")]
public class UserRole
{
[Key]
public int Id { get; set; }
public int RoleId { get; set; }
public int EmployeeId { get; set; }
public bool Active { get; set; }
//fk to employees table
public virtual Employee Employee { get; set; }
//fk to employees table
public virtual Role Role { get; set; }
}
employee.cs
[Table("Tbl_Employees")]
public class Employee
{
[Key]
public int Id { get; set; }
[StringLength(50)]
[Required]
public string UserName { get; set; } = string.Empty;
[StringLength(50)]
[Required]
public string FirstName { get; set; } = string.Empty;
[StringLength(50)]
[Required]
public string LastName { get; set; } = string.Empty;
[StringLength(50)]
[Required]
public string EmployeeNumber { get; set; } = string.Empty;
public int CompanyId { get; set; }
[StringLength(150)]
public string? Email { get; set; }
[StringLength(50)]
public string? PhoneNumber { get; set; }
public string? Department { get; set; }
public string? SubDepartment { get; set; }
public bool Active { get; set; }
//foreign key to UserRoles table
public virtual List<UserRole> UserRoles { get; set; } = new();
}
2. Add configuration in program.cs
:
- connection to the database since I encrypt the connection string in app.config, I use this syntax :
// Add DbContext
builder.Services.AddDbContext<VisitorDataContext>(options =>
{
var context = builder.Configuration.GetConnectionString("Your Connection");
options.UseSqlServer(eAESCrypt.AesMethods.DecryptString(key, context));
});
- configure the app as windows authentication : please be aware "AddNegotiate()" work in .NET 9.0
// Add Windows Authentication
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
- Create Policies for Auhtorization for Claims roles with Microsoft.AspNetCore.Authentication and System.Security.Claims
build configuration for each role (as preconfigured in DB) or policy for combined role
Since we use claims as role : use context.User.HasClaim()
instead of Use.IsInRole()
// Add Authorization
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"))
.AddPolicy("SecurityAdminOnly", policy => policy.RequireRole("SecurityAdmin"))
.AddPolicy("SecurityOnly", policy => policy.RequireRole("Security"))
.AddPolicy("AdminAndSecurityAdminOnly", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(ClaimTypes.Role, "Admin") || context.User.HasClaim(ClaimTypes.Role, "SecurityAdmin")));
//it allow to permit access to a method in the controller for "Admin" or "SecurityAdmin"
3. Create the Class ClaimsTranformation
class to retrieve the Claims role for each user authenticated by windows
public class WVMClaimsTransformation : IClaimsTransformation
{
private readonly VisitorDataContext _context;
public WVMClaimsTransformation(VisitorDataContext context)
{
_context = context;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)principal.Identity;
//get the username without the domain name
var username = Helper.GetUserName(identity.Name);
//get all roles of the specific user from database
var roles = await _context.UserRoles
.Include(ur => ur.Role)
.Where(ur => ur.Employee.UserName == username)
.Select(ur => ur.Role.Name)
.ToListAsync();
//insert it to Claims Identities
foreach (var role in roles)
{
if (!identity.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == role))
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
}
return principal;
}
}
4. Add Authorization to method in Home Controller
for Administration View:
// Add the Authorization to Combine Role : **Admin** or **SecurityAdmin **
[Authorize(Policy = "AdminAndSecurityAdminOnly")]
public IActionResult Administration()
{
return View();
}
When you troobleshooting you can see the role as :
I hope that will help people like it help me. if I missed something please fell free to send me feedback.
Enjoyed this article?
Check out more content on our blog or follow us on social media.
Browse more articles