如何通过Azure AD/OpenID认证并沿用Entity Framework用户角色数据
Great question—this is a super common scenario when migrating legacy Identity apps to Azure AD while keeping your custom user/role data intact. First off, you’re already on the right track using the SecurityTokenValidated notification—this is exactly the hook you need to tie Azure AD’s authentication to your local Identity system. Let’s fix a few key parts of your code to get the role-based authorization working as expected.
Key Adjustments to Make
1. Replace the OIDC Identity with Your Local Identity (Critical for Role Checks)
Right now, you’re adding your local identity as a secondary identity with authManager.User.AddIdentity(ident), but MVC’s AuthorizeAttribute will prioritize the original OIDC identity for role checks. Instead, you need to replace the identity in the authentication ticket with your local one (while retaining important OIDC claims like email/name if needed).
Also, notice you’re using DefaultAuthenticationTypes.ExternalBearer when creating the local identity—you should use CookieAuthenticationDefaults.AuthenticationType instead, since this matches the default sign-in type your cookie middleware expects for subsequent requests.
Here’s the updated SecurityTokenValidated logic:
SecurityTokenValidated = async context => { string userEmail = context.AuthenticationTicket.Identity.Name; // Normalize email to avoid case-sensitivity issues userEmail = userEmail.ToLowerInvariant(); var userManager = context.OwinContext.GetUserManager<AppUserManager>(); var user = await userManager.FindByEmailAsync(userEmail); if (user == null) { Log.Error("User {email} authenticated with Azure AD, but no matching local user found", userEmail); context.HandleResponse(); context.Response.Redirect($"/Error/NoAccess?identity={Uri.EscapeDataString(userEmail)}"); return; } // Update last login time (use UTC to avoid timezone conflicts) user.DateLastLogin = DateTime.UtcNow; IdentityResult result = await userManager.UpdateAsync(user); if (!result.Succeeded) { var errorMsg = string.Join(", ", result.Errors); Log.Error("Failed to update user {email} post-login: {errors}", userEmail, errorMsg); context.HandleResponse(); context.Response.Redirect($"/Error/OtherError?errorDescription={Uri.EscapeDataString(errorMsg)}"); return; } // Create local identity with correct authentication type var localIdentity = await userManager.CreateIdentityAsync( user, CookieAuthenticationDefaults.AuthenticationType ); // Merge important OIDC claims into local identity (optional but useful) var oidcIdentity = context.AuthenticationTicket.Identity; foreach (var claim in oidcIdentity.Claims.Where(c => !localIdentity.HasClaim(c.Type, c.Value))) { // Skip OIDC role claims to avoid interfering with local roles if (claim.Type != "roles") { localIdentity.AddClaim(claim); } } // Replace the ticket's identity with our local/merged identity context.AuthenticationTicket.Identity = localIdentity; }
2. Clean Up Cookie Authentication Configuration
Your current cookie auth setup is minimal—let’s make it explicit to align with our identity type:
app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = CookieAuthenticationDefaults.AuthenticationType, LoginPath = new PathString("/Account/Login"), // Fallback redirect if needed SlidingExpiration = true, // Keep cookie alive for active users ExpireTimeSpan = TimeSpan.FromHours(8) // Adjust based on your security needs });
3. Adjust Token Validation Parameters
Since we’re ignoring Azure AD roles entirely and using local roles, you can remove the RoleClaimType setting from your TokenValidationParameters—it’s no longer needed:
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters { // Remove RoleClaimType here // Optional: Add other validation rules (e.g., ValidateIssuer = true for single-tenant apps) },
Why This Works
- By replacing the OIDC identity in the authentication ticket with your local Identity-generated identity, you ensure that MVC’s
AuthorizeAttributeuses your local roles for authorization checks—exactly like it did with your old form-based auth. - Merging OIDC claims retains useful user data (like display name) without letting Azure AD roles override your local role setup.
- Using the correct authentication type ensures the cookie middleware recognizes the identity on subsequent requests, so users stay logged in and roles are respected across all pages.
Additional Notes
- Email Matching: Always normalize emails (lowercase) when matching Azure AD users to local users—Azure AD may return emails in mixed case, but local databases often store them as lowercase.
- Error Handling: We added better error messaging and URL escaping to avoid invalid characters in redirects.
- UTC Timestamps: Using
DateTime.UtcNowforDateLastLoginprevents timezone inconsistencies across servers.
内容的提问来源于stack exchange,提问作者eddiewould

