如何在已启用Azure AD认证的.NET Core MVC应用中集成自定义PostgreSQL角色存储?
Hey there! Integrating your PostgreSQL-based role store with Azure AD authentication in your .NET Core MVC app is straightforward once you map the authenticated Azure AD user to your local database records. Here's a step-by-step guide tailored to your existing setup:
Azure AD handles the authentication (verifying who the user is), and we’ll layer on authorization logic that pulls roles from your PostgreSQL DB right after the user logs in. The key is linking the Azure AD user to your local Users table, fetching their roles, and injecting those roles into the user’s claims so .NET’s authorization system can use them.
1. Link Azure AD Users to Your Database
First, ensure your Users table has a column to store the Azure AD Object ID (this is a unique, immutable identifier for each Azure AD user). You can add a column like AzureAdObjectId (string type) to your table. When a user first logs in, you’ll store their Azure AD Object ID here to map them to your local user record.
2. Inject Database Roles via Claims Transformation
We’ll use IClaimsTransformation to fetch the user’s roles from PostgreSQL immediately after Azure AD authentication, then add those roles as claims to the user’s identity. This makes the roles available for all authorization checks.
Create a class that implements IClaimsTransformation:
public class DatabaseClaimsTransformer : IClaimsTransformation { private readonly YourDbContext _dbContext; public DatabaseClaimsTransformer(YourDbContext dbContext) { _dbContext = dbContext; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { // Get the Azure AD Object ID from the authenticated user's claims var azureAdObjectId = principal.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier"); if (string.IsNullOrEmpty(azureAdObjectId)) return principal; // Fetch the local user and their associated roles from PostgreSQL var localUser = await _dbContext.Users .Include(u => u.UserRoles) .ThenInclude(ur => ur.Role) .FirstOrDefaultAsync(u => u.AzureAdObjectId == azureAdObjectId); if (localUser == null) return principal; // Create a new claims identity to add our custom roles var claimsIdentity = new ClaimsIdentity(principal.Identity); // Add each database role as a standard "role" claim (recognized by .NET authorization) foreach (var role in localUser.UserRoles.Select(ur => ur.Role.Name)) { claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role)); } return new ClaimsPrincipal(claimsIdentity); } }
Register this transformer in your ConfigureServices method:
services.AddScoped<IClaimsTransformation, DatabaseClaimsTransformer>();
3. Update Authorization Policies
You already have a ConfigurePolicies(services) method—update it to define policies that reference your database roles. For example:
private void ConfigurePolicies(IServiceCollection services) { services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); // "Admin" is a role from your PostgreSQL DB options.AddPolicy("EditorAccess", policy => policy.RequireRole("Editor", "Admin")); }); }
4. Use Authorization in Your App
Now you can enforce these policies in controllers or views:
- On controllers/actions:
[Authorize(Policy = "AdminOnly")] public IActionResult AdminDashboard() { return View(); } - Check roles directly in code:
if (User.IsInRole("Admin")) { // Run admin-specific logic }
5. Optional: Custom Authorization Handler (For Complex Logic)
If you need granular rules (e.g., role-specific permissions tied to resources), create a custom AuthorizationHandler:
public class RolePermissionHandler : AuthorizationHandler<RolePermissionRequirement> { private readonly YourDbContext _dbContext; public RolePermissionHandler(YourDbContext dbContext) { _dbContext = dbContext; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RolePermissionRequirement requirement) { var azureAdObjectId = context.User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier"); if (string.IsNullOrEmpty(azureAdObjectId)) return; var localUser = await _dbContext.Users .Include(u => u.UserRoles) .ThenInclude(ur => ur.Role) .FirstOrDefaultAsync(u => u.AzureAdObjectId == azureAdObjectId); // Check if the user has the required role with the correct permission if (localUser?.UserRoles.Any(ur => ur.Role.Name == requirement.RequiredRole && ur.Role.Permissions.Contains(requirement.RequiredPermission)) ?? false) { context.Succeed(requirement); } } } // Define the requirement public class RolePermissionRequirement : IAuthorizationRequirement { public string RequiredRole { get; } public string RequiredPermission { get; } public RolePermissionRequirement(string requiredRole, string requiredPermission) { RequiredRole = requiredRole; RequiredPermission = requiredPermission; } }
Register it in ConfigureServices:
services.AddScoped<IAuthorizationHandler, RolePermissionHandler>();
And use it in a policy:
options.AddPolicy("CanEditPosts", policy => policy.AddRequirements(new RolePermissionRequirement("Editor", "EditPosts")));
Key Notes
- User Provisioning: Add logic to create a local user record if an Azure AD user logs in for the first time (you can do this in the
ClaimsTransformationclass). - Caching: To avoid hitting PostgreSQL on every request, cache user roles (use
IDistributedCacheor in-memory cache) for better performance. - Claim Consistency: Stick to
ClaimTypes.Rolefor role claims—this is the default type .NET’s authorization system expects.
内容的提问来源于stack exchange,提问作者Roshan

