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; }
Related
In a new API app, going to use JWT bearer tokens for authorization. Idea of storing roles(claims) inside a token great, but there is some issues with using it like this:
Is setting Roles in JWT a best practice?
In our case, main problem would be payload size (100s of roles)
So, we decided to load roles on every request (DB call). However, I am not sure where in execution pipeline this should happen? We would like to use Policies and other built in ASP.NET Core functionality. Somewhere in pipeline we need to place a code to load claims from DB for a user, but before controller executed and policies checked.
I have done something similar, I check the user has the required permission on each request. Mine is done based on Features, but you can change it a bit to be based on Roles.
I have a User with 1 Role, and then Role has N Features.
For this I use created my own AuthorizationHandler as follows. AuthorizationHandlers are at the start of the pipeline, so it will go through all AuthorizationHandlers before hitting the controller endpoint. Offical Docs here
'IFeaturesProvider': is just my Business layer to retrieve features from DB.
ICustomUserContext: Is a wrapper around HttpContextAccessor.HttpContext.User (I will post it at the end of answer for clarity.)
public class FeatureRequirement : IAuthorizationRequirement
{
public string FeatureName { get; set; }
public FeatureRequirement(string featureName)
{
FeatureName = featureName;
}
}
/// <summary>
/// Authorisation based on Feature.
/// A user is assigned a Role, a role can have N Features.
/// If the user is not assigned the Feature required to access the Controller Action this will throw an Authorisation Error -
/// </summary>
public class FeatureAuthorizationHandler : AuthorizationHandler<FeatureRequirement>
{
private readonly IFeaturesProvider _featuresProvider;
private readonly ICustomUserContext _userContext;
public FeatureAuthorizationHandler([FromServices] IFeaturesProvider featuresProvider,
[FromServices] ICustomUserContext userContext)
{
_featuresProvider = featuresProvider;
_userContext = userContext;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FeatureRequirement requirement)
{
// Authentication of user failed - Means Token expired or Token incorrect BUT Token exists in Header
if (!context.User.Identity.IsAuthenticated)
{
return Task.FromResult(0);
}
// get the logged in user id and user type
var userId = _userContext.UserId;
var userType = _userContext.UserType;
// get features assigned to currently logged in user
var userFeatures = _featuresProvider.GetFeaturesByUserId(userId, userType);
// check if user has the required feature assigned to it's Role
if (userFeatures.Select(s => s.Name).Contains(requirement.FeatureName))
context.Succeed(requirement);
return Task.FromResult(0);
}
}
Then in Startup.cs you want to regiseter all your feature/roles as a permission.
services.AddAuthorization(options =>
{
// load all features from DB
var features = rolesProvider.GetAllFeatures(validationContainer);
// Add policy foreach feature in DB
foreach (var feature in features)
{
options.AddPolicy(feature.Name, policy => policy.Requirements.Add(new FeatureRequirement(feature.Name)));
}
});
Also in Startup, register your Depency Injection for the AuthorizationHandler
services.AddScoped<IAuthorizationHandler, FeatureAuthorizationHandler>();
Then in controller you can just do:
[Authorize(Policy = "View Asset Driver")]
public class AssetDriversController : BaseController
{
where "View Asset Driver" is the name of one of my features.
OR
[Authorize(Policy = "View Asset Driver")]
public async Task<IActionResult> GetAssetDrivers(int companyId, int assetId)
Here is the ICustomUserContext:
public interface ICustomUserContext
{
ClaimsPrincipal CurrentUser { get; }
int UserId { get; }
UserTypeEnum UserType { get; }
int ResellerId { get; }
int CompanyId { get; }
int DriverId { get; }
}
public class CustomUserContextAdapter : ICustomUserContext
{
private readonly IHttpContextAccessor _accessor;
public CustomUserContextAdapter(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public ClaimsPrincipal CurrentUser => _accessor.HttpContext.User;
public int UserId => CurrentUser != null ? CurrentUser.GetUserIdInternal() : 0;
public UserTypeEnum UserType => CurrentUser != null ? CurrentUser.GetUserTypeInternal() : UserTypeEnum.User;
public int ResellerId => CurrentUser != null ? CurrentUser.GetResellerIdInternal() : 0;
public int CompanyId => CurrentUser != null ? CurrentUser.GetCompanyIdInternal() : 0;
public int DriverId => CurrentUser != null ? CurrentUser.GetDriverIdInternal() : 0;
}
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.
I'm using the latest VS2019Pro with Core 3.1.
It seems like Blazor ServerApp has real-time code running within the #code{} tags in the .razor pages. So instead of using APIs to provide the data, I was thinking it would make a lot of sense to just create classes and methods to return the data.
The only issue I am facing is being able to use User.Identity.Name in a Class. Usually this is provided in the .razor pages and in Controllers without issue, but how can I (if it's even possible) use the same User.Identity.Name property within classes?
You can use the standard .net core auth: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-3.1
Basically you define a:
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
Then you can await it:
private async Task LogUsername()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
_authMessage = $"{user.Identity.Name} is authenticated.";
}
else
{
_authMessage = "The user is NOT authenticated.";
}
}
Edit -----
Something like this??
Base Component:
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
public String Username {get;set;}
protected override async Task OnInitializedAsync(){
Username = (await authenticationStateTask).User.Identity.Name;
}
Then in your other components:
#inherits BaseCompomnent
#Username
IMyRoleManager _myRoleManager;
private AuthenticationStateProvider _authenticationStateProvider;
public MyRepository(IMyRoleManager myRoleManager, AuthenticationStateProvider authentication)
{
_myRoleManager = myRoleManager;
_authenticationStateProvider = authentication;
}
public async Task<bool> AddRoleToUser(string role)
{
var id = await GetUserIdFromAuthenticationStateProviderAsync();
var result = await _myRoleManager.SetRoleForUser(role, id);
return result;
}
And in the startup file the correct entries have to be there for services.xxx (identity services)
I've got Asp.Net Identity 2 all set up and running with a custom user store backed by SQL Server via Dapper. At this point in my dev/testing I'm only concerned with local accounts (but will be adding in external login providers). I have a custom user that includes the standard properties that Asp.Net Identity wants, and added a few of my own (FirstName, LastName):
public class AppUser : IUser<Guid>
{
public Guid Id { get; set; }
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string SecurityStamp { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public bool LockoutEnabled { get; set; }
public DateTimeOffset LockoutEndDate { get; set; }
public int AccessFailedCount { get; set; }
// Custom User Properties
public string FirstName { get; set; }
public string LastName { get; set; }
}
In my MVC web app, I configure the OIDC like so:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["OpenIdConnectAuthenticationOptions.Authority"],
ClientId = "MVC.Web",
Scope = "openid profile email",
RedirectUri = ConfigurationManager.AppSettings["OpenIdConnectAuthenticationOptions.RedirectUri"],
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
Since I included profile as a requested scope, I get:
preferred_username: testuser
And since I included email as a requested scope, I get:
email: user#test.com
email_verified: true
I'm not explicitly telling my AspNetIdentityUserService how to map the UserName property in my AppUser to the preferred_username claim and I'm not sure how that happens. Therefore, I don't understand how to get the FirstName property mapped to the given_name claim so that it will be returned with the id_token.
What I've researched:
So if you look at the IdentityServer3 AspNetIdentity sample here I found this ClaimsIdentityFactory which looked like it should do the trick:
public override async Task<ClaimsIdentity> CreateAsync(UserManager<User, string> manager, User user, string authenticationType)
{
var ci = await base.CreateAsync(manager, user, authenticationType);
if (!String.IsNullOrWhiteSpace(user.FirstName))
{
ci.AddClaim(new Claim("given_name", user.FirstName));
}
if (!String.IsNullOrWhiteSpace(user.LastName))
{
ci.AddClaim(new Claim("family_name", user.LastName));
}
return ci;
}
So I added this in to my app and wired it up in my custom UserManager. And I do hit a breakpoint when the class is instantiated, but I don't hit a breakpoint ever on the CreateAsync method and my claims aren't returned.
I also saw this IdentityServer3 Custom User sample here, and I found this GetProfileDataAsync method that looked like it might be the right thing (but it seems like I'm digging deeper than I should be for something seemingly so simple/common):
public override Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// issue the claims for the user
var user = Users.SingleOrDefault(x => x.Subject == context.Subject.GetSubjectId());
if (user != null)
{
context.IssuedClaims = user.Claims.Where(x => context.RequestedClaimTypes.Contains(x.Type));
}
return Task.FromResult(0);
}
I had the same problem here, in that a breakpoint in this method was never tripped. I even went so far as to look at the IdentityServer3 source code, and see that this only gets called if the scope has the IncludeAllClaimsForUser flag set. But I'm using the standard profile scope here, so I started to question whether I needed to make my own definition for a profile scope that has the IncludAllClaimsForUser flag set, or if there was a way to add that flag to the standard scope.
And to add to all of this... This only needs to be done when using a local account. When I implement external login providers, I'll ask for the profile there, and expect to be able to get a first and last name. So then I wonder what happens once I've already got those claims (or how to determine whether I need to pull them from my user store or not). Seems like I need to hook in to something that only runs when doing a local login.
And then I started to really question whether I'm going about this the right way since I'm seeing/finding so little info on this (I would have expected this to be a fairly common scenario that others have already implemented, and expected to find docs/samples). Been trying to resolve this for a day now. Hopefully someone has a quick answer/pointer!
I use OpenIdConnectAuthenticationNotifications to achieve this, you could connect to ASP.NET Identity database or do anything in there, here is a sample code I use for one of my project:
This is a complete source code from my Startup.cs, but what you really need is just the SecurityTokenValidated section ...
using System.Configuration;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Helpers;
using IdentityServer3.Core;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
namespace MyProject
{
public partial class Startup
{
public static string AuthorizationServer => ConfigurationManager.AppSettings["security.idserver.Authority"];
public void ConfigureOAuth(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
jwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
SecurityTokenValidator = jwtSecurityTokenHandler,
Authority = AuthorizationServer,
ClientId = ConfigurationManager.AppSettings["security.idserver.clientId"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["security.idserver.postLogoutRedirectUri"],
RedirectUri = ConfigurationManager.AppSettings["security.idserver.redirectUri"],
ResponseType = ConfigurationManager.AppSettings["security.idserver.responseType"],
Scope = ConfigurationManager.AppSettings["security.idserver.scope"],
SignInAsAuthenticationType = "Cookies",
#if DEBUG
RequireHttpsMetadata = false, //not recommended in production
#endif
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
},
SecurityTokenValidated = n =>
{
var id = n.AuthenticationTicket.Identity;
//// we want to keep first name, last name, subject and roles
//var givenName = id.FindFirst(Constants.ClaimTypes.GivenName);
//var familyName = id.FindFirst(Constants.ClaimTypes.FamilyName);
//var sub = id.FindFirst(Constants.ClaimTypes.Subject);
//var roles = id.FindAll(Constants.ClaimTypes.Role);
//// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
id.AuthenticationType,
Constants.ClaimTypes.Name,
Constants.ClaimTypes.Role);
nid.AddClaims(id.Claims);
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
nid.AddClaim(new Claim("access_Token", n.ProtocolMessage.AccessToken));
////nid.AddClaim(givenName);
////nid.AddClaim(familyName);
////nid.AddClaim(sub);
////nid.AddClaims(roles);
////// add some other app specific claim
// Connect to you ASP.NET database for example
////nid.AddClaim(new Claim("app_specific", "some data"));
//// keep the id_token for logout
//nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
return Task.FromResult(0);
}
}
});
//app.UseResourceAuthorization(new AuthorizationManager());
}
}
}
The (A) correct answer to this question is to override the GetProfileDataAsync method in the AspNetIdentityUserService class like so:
public class AppUserService : AspNetIdentityUserService<AppUser, Guid>
{
private AppUserManager _userManager;
public AppUserService(AppUserManager userManager)
: base(userManager)
{
_userManager = userManager;
}
public async override Task GetProfileDataAsync(ProfileDataRequestContext ctx)
{
var id = Guid.Empty;
if (Guid.TryParse(ctx.Subject.GetSubjectId(), out id))
{
var user = await _userManager.FindByIdAsync(id);
if (user != null)
{
var claims = new List<Claim>
{
new Claim(Constants.ClaimTypes.PreferredUserName, user.UserName),
new Claim(Constants.ClaimTypes.Email, user.Email),
new Claim(Constants.ClaimTypes.GivenName, user.FirstName),
new Claim(Constants.ClaimTypes.FamilyName, user.LastName)
};
ctx.IssuedClaims = claims;
}
}
}
}
But as I had discovered, this wasn't enough. Looking at the source code for IdentityServer, you will find this bit:
if (scopes.IncludesAllClaimsForUserRule(ScopeType.Identity))
{
Logger.Info("All claims rule found - emitting all claims for user.");
var context = new ProfileDataRequestContext(
subject,
client,
Constants.ProfileDataCallers.ClaimsProviderIdentityToken);
await _users.GetProfileDataAsync(context);
var claims = FilterProtocolClaims(context.IssuedClaims);
if (claims != null)
{
outputClaims.AddRange(claims);
}
return outputClaims;
}
Notice that GetProfileDataAsync won't be called unless there is a flag set to include all claims (not sure why they chose to do it this way, but obviously there must be a good reason!). So I thought that meant that I needed to completely redefine the profile scope, but with further digging in the source I found this was not the case. The StandardScopes has a method that creates the scopes with the always include flag set. Instead of setting your scopes doing this:
factory.UseInMemoryScopes(StandardScopes.All);
Do this:
factory.UseInMemoryScopes(StandardScopes.AllAlwaysInclude);
THEN your GetProfileDataAsync will be run and you will get all of your claims!
Note: My first try using ClaimsIdentityFactory wasn't ever going to work, as I'm not logging in to Asp.Net Identity, and it makes sense that this wouldn't ever get called unless that is what I was doing.
Note: #Rosdi Kasim's answer is certainly valid if you desire to add claims (app specific claims especially) after you have already received your id_token from Identity Server.
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.