Find User Role ASP.Net Core - c#

Trying to find out where roles are assigned to a particular user in an ASP.Net core app. I'm new to .net core so bare with me. I can debug and see the username and password but cannot figure out where the roles are set for this particular user? If there's something else I need to add here please advise.
I use the login provide and get "No roles assigned." below for the user. I have in the Login.cshtml.cs page:
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var user = _signInManager.UserManager.Users.Where(a => a.Email == Input.Email).FirstOrDefault();
if (user == null || !user.IsActive)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
else
{
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Any())
{
ModelState.AddModelError(string.Empty, "No roles assigned.");
return Page();
}
if ((returnUrl ?? "").Length < 1)
{
returnUrl = await GetRoleHomepage(user);
}
}
return RedirectToPage(returnUrl);
}
Honestly, I'm used to website's, the old school pages because I'm coming from local government which is far behind private. Now I'm private and now stressed. Looking at the AccountController.cs I see:
[Route("[controller]/[action]")]
public class AccountController : Controller
{
private readonly SignInManager<AspNetUsers> _signInManager;
private readonly ILogger _logger;
public AccountController(SignInManager<AspNetUsers> signInManager, ILogger<AccountController> logger)
{
_signInManager = signInManager;
_logger = logger;
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
return RedirectToPage("/Index");
}
}
Thanks for the input: The only part I see with IdentityRole is in a class with this but it's empty:
using Microsoft.AspNetCore.Identity;
namespace Portal.HPAG.Data
{
public partial class AspNetRoles : IdentityRole
{
}
}
And then I found a AspNetRoles class:
using System;
using System.Collections.Generic;
namespace Portal.HPAG.Data
{
public partial class AspNetRoles
{
public AspNetRoles()
{
AspNetUserRoles = new HashSet<AspNetUserRoles>();
PartStatusTypeRoles = new HashSet<PartStatusTypeRoles>();
RoleLevels = new HashSet<RoleLevels>();
Stage = new HashSet<Stage>();
}
public string Id { get; set; }
public bool IsManager { get; set; }
public virtual ICollection<AspNetUserRoles> AspNetUserRoles { get; set; }
public virtual ICollection<PartStatusTypeRoles> PartStatusTypeRoles { get; set; }
public virtual ICollection<RoleLevels> RoleLevels { get; set; }
public virtual ICollection<Stage> Stage { get; set; }
}
}
Still digging through.

In addition to AddToRoleAsync(TUser user, string role) and IsInRoleAsync, you should be able to iterating through roles in an IEnumerable<IdentityRole> variable.
You should be able to set a new variable for _roleManager using:
serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();

You can learn how to use UserManager and RoleManager to manager your user and role. There are many methods encapsulated in Them. You can use UserManager and RoleManager via dependency injection like signInManager.
I list a few very common methods below:
Add role in user: userManager.AddToRoleAsync(user, role.Name);
Add the specified user to the named roles:
userManager.AddToRolesAsync(user, IEnumerable<role.Name>)
Returns a list of users from the user store who are members of the
specified roleName: userManager.GetUsersInRoleAsync(role.Name)
Returns a flag indicating whether the specified user is a member of
the given named role: userManager.IsInRoleAsync(user, role.Name)
There are other methods that I haven't listed, you can check them out through the hyperlinks I provided.

Related

Add claims to a user during login using Identity in ASP.NET Core web app

Edit 2:
Finally figured out. Check out the accepted answer below.
Edit:
Full source code with my attempt as instructed by #Jason Pan.
https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin
Added Claims during Login (in Login.cshtml.cs file) and accessed those claims from Razor Component.
Unfortunately, it didn't work. I only get null as the claim value. 😔
Original Question
I've seen some similar questions like this and this, but they weren't helpful for my scenario.
My application is a Blazor Server project where I have added Identity following the steps mentioned here.
Now this is what I want to achieve:
User enters their credentials.
If the username is valid (in our Active Directory), I retrieve a field known as EmployeeId from the Active Directory.
Authenticate the user using SignInManager.PasswordSignInAsync.
Add EmployeeId as a claim to the ClaimsPrincipal. (So that I can use EmployeeId from Razor Components like this).
I'm struggling to figure out how to add this EmployeeId as claims during the login process.
My OnPostAsync method in Login.cshtml.cs looks like this:
public class LoginModel : PageModel
{
private readonly SignInManager<MMTUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(SignInManager<MMTUser> signInManager, ILogger<LoginModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
if (ModelState.IsValid)
{
// Step 1: Check if this user exists in our AD
// If YES: Grab the Employee Id and go to next step
// If NO: Terminate the process
var adLookupResult = ADHelper.ADLookup(Input.Username);
if (adLookupResult == null || string.IsNullOrEmpty(adLookupResult.EmployeeId))
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
// Step 2: SignIn the user
var result = await _signInManager.PasswordSignInAsync(Input.Username, Input.Password, isPersistent: Input.RememberMe, lockoutOnFailure: false);
// Step 3: How do I add adLookupResult.EmployeeId to the ClaimsPrincipal?
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
public class InputModel
{
[Required]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}
UPDATE
The previous solution could be a workaround if put in middleware. That very terrible.
We should use CustomClaimsPrincipalFactory to fix the issue.
CustomClaimsPrincipalFactory.cs
using HMT.Web.Server.Areas.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
namespace HMT.Web.Server
{
public class CustomClaimsPrincipalFactory :
UserClaimsPrincipalFactory<HMTUser>
{
public CustomClaimsPrincipalFactory(
UserManager<HMTUser> userManager,
IOptions<IdentityOptions> optionsAccessor)
: base(userManager, optionsAccessor)
{
}
// This method is called only when login. It means that "the drawback
// of calling the database with each HTTP request" never happen.
public async override Task<ClaimsPrincipal> CreateAsync(HMTUser user)
{
var principal = await base.CreateAsync(user);
if (principal.Identity != null)
{
//((ClaimsIdentity)principal.Identity).AddClaims(
// new[] { new Claim("EmpId", user.EmpId) });
((ClaimsIdentity)principal.Identity).AddClaims(
new[] { new Claim("EmpId", "EmpId123") });
}
return principal;
}
}
}
Program.cs or Startup.cs
// Learned the hard way that this needs to be added after setting up Blazor (i.e. AddRazorPages, AddServerSideBlazor) - AshishK
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<HMTUser>>();
builder.Services.AddScoped<IUserClaimsPrincipalFactory<HMTUser>, CustomClaimsPrincipalFactory>();
For more details, you can check the link.
When you login uscessfully, you can use HttpContext.User.AddIdentity to achieve that. Sample like below:
// Step 3: Add the EmployeeId to ClaimsPrincipal
if (result.Succeeded)
{
HttpContext.User.AddIdentity(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, adLookupResult.EmployeeId)
}));
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
Finally figured this out by asking a question in aspnetcore github repo.
https://github.com/dotnet/aspnetcore/issues/46558
Much thanks to #Piotr Stola and #Jason Pan for their help!
Step 1:
In OnPostAsync method in Login.cshtml.cs, use _signInManager.SignInWithClaimsAsync instead of _signInManager.PasswordSignInAsync.
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
if (ModelState.IsValid)
{
// Step 1: Check if this user exists in our AD
// If YES: Grab the Employee Id and go to next step
// If NO: Terminate the process
var adLookupResult = // Call Active Directory and get EmployeeId of this user here
if (adLookupResult == null || string.IsNullOrEmpty(adLookupResult.EmployeeId))
{
ModelState.AddModelError(string.Empty, "User doesn't exist.");
return Page();
}
// Step 2: Check if the user exists in our Identity database (AspNetUsers table)
var user = await _signInManager.UserManager.FindByNameAsync(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "User doesn't exist.");
return Page();
}
// Step 3: Check the credentials of this user.
var result = await _signInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: false);
if (result.Succeeded)
{
// Access 'EmployeeId' from Areas/Identity/Components/TakeABreak.razor
// For eg: Emp123 is adLookupResult.EmployeeId that I retrieved from Active Directory in Step 1.
var customClaims = new[] { new Claim("EmployeeId", "Emp123") };
await _signInManager.SignInWithClaimsAsync(user, Input.RememberMe, customClaims);
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
Step 2:
Access this from any Razor component. For eg: I'm accessing it from TakeABreak.razor component (Line Number 15 in the attached picture).
Full Source Code
https://github.com/affableashish/blazor-server-auth/tree/feature/AddClaimsDuringLogin

How to make a custom Authentication in Blazor

Hello
l
I am stuck with this problem.
I am building a blazor server side page. I am have a custom login form where I call my api to check if the user login attempt was successful.
This is my login form
After a successful login, the user is redirected to another page. On this page, the navMenu is supposed to change.
I already understand that I need to override my AuthenticationStateProvider. But I dont understand how this works.
When the user inputs his informations a new object named user is created
namespace AdminFrontend.Services
{
public class User
{
//"email" & "password" must be lowercase because the json
needs small initial letters
public string email { get; set; }
public string password { get; set; }
public string mobilenumber { get; set; }
public string service { get; set; }
public override string ToString()
{
return $"{email}: {password}: {mobilenumber}: {service}";
}
}
}
How can i authorize the user after he logged in to see everything in the
<Authorized> Tag.
If you need some more informations let me know :D
Thanks for all answers
First all info i will post it's based on Microsoft Docs
AuthenticationStateProvider : Blazor has a built-in service called AuthenticationStateProvider service which obtains authentication state data from ASP.NET Core's and is used by AuthorizeView component and CascadingAuthenticationState component to get the authentication state. So based on that, we need to modify the core one to create or own authentication state, that inherits from main one (to get access to their main functionalities) so we need to do the following:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
public CustomAuthenticationStateProvider()
{
//Anonymous User
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Sid, "0"),
new Claim(ClaimTypes.Name, "Anonymous"),
new Claim(ClaimTypes.Role, "Anonymous")
}, null);
this.CurrentUser = ClaimsPrincipal(identity);
}
private ClaimsPrincipal CurrentUser { get; set; }
//Set Claims
private ClaimsPrincipal GetUserClaim(string userName, string id, string role)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes. Sid, id),
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Role, role)
}, "Authentication type");
return new ClaimsPrincipal(identity);
}
//Principal function to use (to get the user with AuthenticationState.User)
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var task = Task.FromResult(new AuthenticationState(this.CurrentUser));
return task;
}
public Task<AuthenticationState> ChangeUserClaim(string username, string id, string role)
{
this.CurrentUser = this.GetUser(username, id, role);
var task = this.GetAuthenticationStateAsync();
this.NotifyAuthenticationStateChanged(task);
return task;
}
}
Now with our custom Provider we need to add it on our startup.cs//program.cs (net 5 vs net 6)
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
And finally you can use and on your view! just remember to add this on the razor pages
[CascadingParameter] public Task<AuthenticationState> AuthTask { get; set; }
[Inject] private AuthenticationStateProvider AuthState { get; set; }

Why do the ASP.NET Core password validation rules have no effect?

I'm building an ASP.NET Core 5 Web API using ASP.NET Core Identity on top a custom data access layer that makes use of the Dapper ORM. Fundamentally, things work as expected but I realized that the password validation rules provided by the Identity framework have no effect whatsoever and I fail to understand what is going on. Here is what I have:
First, because I rely on a custom data access layer I provide a custom implementation of Identity's IUserStore interface.
public class UserStore : IUserStore<AppUser>,
IUserPasswordStore<AppUser>,
IUserEmailStore<AppUser>
{
private IRepository<AppUser> _repository;
public UserStore(IConfiguration configuration)
{
_repository = new AppUserRepository(configuration.GetConnectionString("MyConnectionString"));
}
// IUserStore implementation
// IUserPasswordStore implementation
// IUserEmailStore implementation
}
Next, there is a binding model that is used to submit the information required to create new accounts.
public class RegisterBindingModel
{
[Required]
[Display(Name = "UserName")]
public string UserName
{
get;
set;
}
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password
{
get;
set;
}
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare(nameof(Password), ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword
{
get;
set;
}
// remaining required properties
}
Next, new accounts are created via the AccountController:
[Authorize]
[ApiController]
[Route("api/Accounts")]
public class AccountController
{
private readonly UserManager<AppUser> _userManager;
private readonly IPasswordHasher<AppUser> _passwordHasher;
public AccountController(UserManager<AppUser> userManager, IPasswordHasher<AppUser> passwordHasher)
{
_userManager = userManager;
_passwordHasher = passwordHasher;
}
[AllowAnonymous]
[HttpPost]
[Route("Register")]
public async Task<ActionResult> Register([FromBody]RegisterBindingModel model)
{
if(model == null)
{
return BadRequest();
}
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new AppUser()
{
UserName = model.UserName,
Firstname = model.Firstname,
Lastname = model.Lastname,
Email = model.Email,
Gender = model.Gender
};
user.PasswordHash = _passwordHasher.HashPassword(user, model.Password);
IdentityResult result = await _userManager.CreateAsync(user);
return GetHttpResponse(result);
}
[AllowAnonymous]
[HttpPost]
[Route("Token")]
public async Task<IActionResult> Login([FromForm]LoginBindingModel model)
{
if(model == null)
{
return BadRequest();
}
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
AppUser user = await _userManager.FindByNameAsync(model.UserName);
if(user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
{
return Unauthorized();
}
DateTime currentTime = DateTime.UtcNow;
JwtSecurityTokenHandler jwtTokenHandler = new();
SecurityTokenDescriptor tokenDescriptor = new()
{
Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, user.AppUserId.ToString()) }),
IssuedAt = currentTime,
Expires = currentTime.AddHours(_accessTokenValidityInHours),
SigningCredentials = _signingCredentialsProvider.GetSigningCredentials()
};
return Ok(jwtTokenHandler.WriteToken(jwtTokenHandler.CreateToken(tokenDescriptor)));
}
...
}
Finally, things are wired together as follows:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration
{
get;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityCore<AppUser>(options => Configuration.GetSection(nameof(IdentityOptions)).Bind(options));
services.AddScoped<IPasswordHasher<AppUser>, Identity.PasswordHasher<AppUser>>();
services.AddTransient<IUserStore<AppUser>, UserStore>();
...
}
}
The corresponding settings are stored in the appsettings.json file:
{
"IdentityOptions": {
"Password": {
"RequiredLength": 6,
"RequiredUniqueChars": 6,
"RequireNonAlphanumeric": true,
"RequireUppercase": true,
"RequireLowercase": true,
"RequireDigit": true
},
"Lockout": {
"AllowedForNewUsers": true,
"MaxFailedAccessAttempts ": 5,
"DefaultLockoutTimeSpan ": "00:05:00"
}
},
...
}
If I send an HTTP POST request with the necessary account data it literally doesn't matter what the password is. The call succeeds even if I just put 1 as a password which clearly violates the password rules. The statement if(!ModelState.IsValid) happily tells me that everything is fine with the model.
From what I see, ASP.NET Core Identity provides a PasswordValidator that apparently is supposed to validate the password according to the provided settings. That validator does not run in my setup, judging from the results that I get.
It is unclear to me whether things should just work they way they are or whether I need to implement something that I'm unaware of. Does anyone have more insight and can tell me what I'm missing here?
Edit:
I just realized that the default UserManager exposes a list of IPasswordValidator objects. Is the idea that I use that list to validate the password in my Register method of the AccountController?
I ended up modifying the Register method as follows:
[AllowAnonymous]
[HttpPost]
[Route("Register")]
public async Task<ActionResult> Register([FromBody]RegisterBindingModel model)
{
if(model == null)
{
return BadRequest();
}
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
AppUser user = new()
{
UserName = model.UserName,
Firstname = model.Firstname,
Lastname = model.Lastname,
Email = model.Email,
Gender = model.Gender
};
IdentityResult result;
foreach(IPasswordValidator<AppUser> passwordValidator in _userManager.PasswordValidators)
{
result = await passwordValidator.ValidateAsync(_userManager, user, model.Password);
if(!result.Succeeded)
{
return BadRequest(result.Errors);
}
}
user.PasswordHash = _userManager.PasswordHasher.HashPassword(user, model.Password);
result = await _userManager.CreateAsync(user);
return GetHttpResponse(result);
}
The default UserManager contains the PasswordValidators property which allows me to access all PasswordValidators. I just loop through them and call the ValidateAsync method on the password submitted by the user.
The reason your if(!ModelState.IsValid) does not see anything wrong comes from the fact that the Password parameter from your model RegisterBindingModel does not contain the same validation as your Options on your appsettings.json, you only verify that it is required (so one character is ok).
If you want to have the same validation you need to add more attributes on the Password parameter. I advise you to look this https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-5.0

Registering and update model in a single API call

I have an ASP.net API 2 application with a register method, which basically does the following:
var user = new ApplicationUser() { UserName = user.email, Email = user.email };
UserManager.AddToRole(user.Id, "SomeRole");
In the same controller, I would like to assign a model class to the application user, for example:
var parent = db.Parents.Find(parentToCreate.id);
db.SaveChanges();
This gives me an error that the user name is already taken. I know that the issue is relating to there being two model trackers, one for the UserManager.CreateAsync and one for updating the db. Will it be alright to create users without using CreateAsync or is there another way to avoid this error?
Note that I think that this could be achieved by putting a Parent property on the account property, but not all accounts are parents, so I do not want to do this solution. A parent has an account, so there is an account property on the parent.
As requested, the full register method is as follows:
[AllowAnonymous]
[Route("RegisterParent")]
public async Task<IHttpActionResult>RegisterParent(ParentRegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.email, Email = model.email };
Parent parentToCreate = new Parent();
db.Parents.Add(parentToCreate);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.SaveChanges();
try
{
IdentityResult result = await UserManager.CreateAsync(user, model.password);
// The following two lines give an error
parentToCreate.account = user;
// the above two lines give an error
UserManager.AddToRole(user.Id, "ParentRole");
db.SaveChanges();
}
catch (Exception e)
{
Console.Write(e);
// e returns here with message
}
return Ok(200);
}
This is a simplified example based on minimal example provided in OP.
Based on conversation to clarify current design the Parent model would need to be updated to have a proper code first Foreign Key relationship
public class Parent {
//...other properties
//Foreign key
public string accountid { get; set; }
//navigation property
[ForeignKey("accountid")]
public ApplicationUser account { get; set; }
}
With that then you only need to assign the user id when creating the parent.
Refactoring/abstracting out specific responsibilities.
public interface IParentService {
Task AddParentAsync(ApplicationUser user);
}
public class ParentService : IParentService {
ApplicationDbContext db;
public ParentService(ApplicationDbContext db) {
this.db = db;
}
public async Task AddParentAsync(ApplicationUser user) {
Parent parentToCreate = new Parent() {
//...set other properties
accountid = user.Id
};
db.Parents.Add(parentToCreate);
await db.SaveChangesAsync();
}
}
Next separating the action into distinct processes to avoid concurrency issues.
public class AccountController : ApiController {
ApplicationUserManager userManager;
IParentService parentService;
public AccountController(ApplicationUserManager userManager, IParentService parentService) {
this.userManager = userManager;
this.parentService = parentService;
}
[AllowAnonymous]
[Route("RegisterParent")]
public async Task<IHttpActionResult> RegisterParent(ParentRegisterBindingModel model) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.email, Email = model.email };
var result = await userManager.CreateAsync(user, model.password);
if (result.Succeed) {
try {
await userManager.AddToRole(user.Id, "ParentRole");
await parentService.AddParentAsync(user);
return Ok();
} catch (Exception e) {
userManager.Delete(user.Id);
Console.Write(e);
// e returns here with message
return BadRequest(); //OR InternalServerError();
}
} else {
foreach (var error in result.Errors) {
ModelState.AddModelError("", error);
}
return BadRequest(ModelState);
}
}
}
You would obviously register dependencies with the DI framework to allow for proper injection.

MVC 5 Identity 2.0 Allow Login using Email or Recovery Email

The requirement for the website is the ability to login using the email address or a recovery email address in a single text box. I have extended the user.identity property to include a recover email. I have also added an update to the AccountController to check the email address entered against the email and the RecoveryEmail field. However, I need to modify the email validation upon registration because it cannot exist in the recovery email field either. From another post, I have created a CustomUserValidation but I cannot access the new identity field.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("RecoveryEmail", this.RecoveryEmail.ToString()));
return userIdentity;
}
public string RecoveryEmail { get; set; }
}
Then I created an extension
namespace TestSecurity.Extensions
{
public static class IdentityExtensions
{
public static string GetRecoveryEmail(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("RecoveryEmail");
return (claim != null) ? claim.Value : string.Empty;
}
}
}
Now I am trying to create a CustomUserValidator class that requires when registering an email, it has to be unique within Email field but also within RecoveryEmail.
public class CustomUserValidator<TUser> : IIdentityValidator<TUser>
where TUser : class, Microsoft.AspNet.Identity.IUser
{
private readonly UserManager<TUser> _manager;
public CustomUserValidator()
{
}
public CustomUserValidator(UserManager<TUser> manager)
{
_manager = manager;
}
public async Task<IdentityResult> ValidateAsync(TUser item)
{
var errors = new List<string>();
if (_manager != null)
{
//check if email exists in recovery email
var findEmail= await _manager.FindByEmailAsync(item.UserName);
var findRecoveryEmail = ???
}
return errors.Any()
? IdentityResult.Failed(errors.ToArray())
: IdentityResult.Success;
}
Task<IdentityResult> IIdentityValidator<TUser>.ValidateAsync(TUser item)
{
throw new NotImplementedException();
}
}
I am not certain what to add in the ???. I have added the reference using TestSecurity.Extensions, but I cannot access this RecoveryEmail field to search.
Thank you for your help.

Categories

Resources