I'm trying to build a multi tenant application in asp.net core and registered asp.net identity like this:
services.AddIdentity<TUserIdentity, TUserIdentityRole>(options =>
{
options.User.RequireUniqueEmail = false;
})
.AddUserValidator<MultitenantUserValidator<TUserIdentity>>()
.AddEntityFrameworkStores<TContext>()
.AddDefaultTokenProviders();
My MultitenantUserValidator class:
public class MultitenantUserValidator<TUser> : IUserValidator<TUser>
where TUser : UserIdentity
{
public async Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user)
{
bool combinationExists = await manager.Users
.AnyAsync(x => x.UserName == user.UserName
&& x.Email == user.Email
&& x.TenantId == user.TenantId);
if (combinationExists)
return IdentityResult.Failed(new IdentityError { Description = "The specified username and email are already registered in the given tentant" });
return IdentityResult.Success;
}
}
This is a part of my DbSeedingHelper Class
var result = await userManager.CreateAsync(user, Users.AdminPassword);
if (result.Succeeded)
{
// Add user to role
var userRoleresult = await userManager.AddToRoleAsync(user, AuthorizationConsts.AdministrationRole);
}
When it executes the line to add the created user to role, the identity result fails with the error from the MultitenantUserValidator: "The specified username and email are already registered in the given tenant".
I don't know why it goes back to the validator when trying to add the already created user to a role. I thought it's only meant to be called when the user is being created. Because of this, I can't add the user to a role. How can I solve this?
An identity user is validated whenever it is updated, not just when it is created. This is to ensure that the user stays valid at all times.
This obviously means that your validator should be able to verify existing users as well. A logic that checks for matching combinations, which would include the current user itself, is obviously not a good idea there.
A better check would be to make sure that there is no user with the same combination that isn’t the same user.
bool combinationExists = await manager.Users.AnyAsync(x => x.Id != user.Id && …);
Note that both user name and email address are usually unique within ASP.NET Core Identity. So it is impossible for users to have two distinct users with the the same user name and email address.
If you want your multi-tenancy to be based on the fact that you can register with a single username/email onto multiple tenants but have a separate identity, then this won’t work with the default setup.
Related
I'm making a web application with ASP .NET Core 3.1. I'm using Identity to build user login function.
I'm getting a problem in which registered user can't login, due to password checking always fail. I went ahead and create an user programmatically, then check password right after the creation, and the check still fail.
Did I forget to set something up?
More details:
I want user with roles so I set up custom Identity service like this. I think I forgot to set up some extra thing here.
// In Startup class
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddIdentity<IdentityUser, IdentityRole>((_options) =>
{
// Custom IdentityOptions configuration;
})
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
// ...
}
I tried create user programmatically and check the password right after that. The check still fail.
// In Startup.Configure() method
string username = "admin";
string password = "123456";
IdentityUser user = new IdentityUser(username);
await userManager.CreateAsync(user, password);
bool isCorrect = await userManager.CheckPasswordAsync(user, password);
// isCorrect is always false;
(I do make sure that the user is created successfully, and password is added successfully. I did view the database data and see the user there.)
I did check similar questions on StackOverflow, but their problem seems to be different from mine so it didn't help.
UserManager.CreateAsync method returns an IdentityResult that will tell you if user creation succeeded or not.
string username = "admin";
string password = "123456";
IdentityUser user = new IdentityUser(username);
IdentityResult result = await userManager.CreateAsync(user, password);
if (result.Succeded)
{
var actualUser = await userManager.FindByNameAsync(username);
if (actualUser != null)
{
bool isCorrect = await userManager.CheckPasswordAsync(actualUser, password);
}
}
else
{
// check what went wrong
var errors = result.Errors;
}
User creation is probably failing because password is weak.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.usermanager-1.createasync?view=aspnetcore-6.0
This question already has answers here:
JWT Authentication - UserManager.GetUserAsync returns null
(3 answers)
Closed 2 years ago.
I am using asp.net mvc with work/school accounts authentication. Currently I'm trying to implement identity into to the user process.
Here is my ApplicationUser class:
public class ApplicationUser: IdentityUser
{
public ICollection<Semester> Semesters { get; set; }
}
So far, identity works just fine, there is just one problem. When I log into the app with my school account, I can call the ClaimsPrincipals as User in the Controllers. To get the current ApplicationUser you can use the UserManager (await _userManager.GetUserAsync(User), with User being the ClaimsPrincipals) But since I haven't stored my school account in the database, the result will be null. If I create a new ApplicationUser like the following
var newUser = new ApplicationUser()
{
UserName = User.Identity.Name,
Email = User.Identity.Name
};
await _userManager.CreateAsync(newUser);
await _userManager.AddClaimsAsync(newUser, User.Claims);
This will succesfully create and save the new user to the database with the claims. But then when I try to get the new created ApplicationUser with await _userManager.GetUserAsync(User) the result will still be null. If I access my DbContext and get all ApplicationUsers, the newly created ApplicationUser is there. So, how can I create an ApplicationUser based on the ClaimsPrincipals I get from my school account login?
Credits to #poke for this.
UserManager.GetUserAsync internally uses UserManager.GetUserId to retrieve the user id of the user which is then used to query the object from the user store (i.e. your database).
GetUserId basically looks like this:
public string GetUserId(ClaimsPrincipal principal)
{
return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType);
}
So this returns the claim value of Options.ClaimsIdentity.UserIdClaimType. Options is the IdentityOptions object that you configure Identity with. By default the value of UserIdClaimType is ClaimTypes.NameIdentifier, i.e. "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier".
So when you try to use UserManager.GetUserAsync(HttpContext.User), where that user principal has a UserID claim, the user manager is simply looking for a different claim.
You can fix this by either switchting to the ClaimTypes.NameIdentifier:
new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
})
Or you configure Identity properly so it will use your UserID claim type:
// in Startup.ConfigureServices
services.AddIdentity(options => {
options.ClaimIdentity.UserIdClaimType = "UserID";
});
Source:
https://stackoverflow.com/a/51122850/3850405
The claims from an external provider will be specific to that provider. It is not logging into the local identity store in your app, it is just claiming to know who the user is. So you need to log the user to your store (SignInManager) before you can use it for authorization. If you don't care about protecting resources and just want to know the user you can directly map to your internal store
The claims in the header need to be intercepted by the ASPNET 'middleware' using an authentication provider which will then set the User object in the HttpContext. Once you have the user, you would need to map your local user store to those from the school account, then get the claims as a separate call from the result. Usually the email is the subject claim and can be used for mapping:
var userName = User.Identity.Name;
var user = _userManager.FindByNameAsync(userName);
var claims = _userManager.GetClaimsAsync(user);
I am using Razor-Pages to develop a web app. In my _Layout.cshtml file, I want to change the menu according to the role of the current user.
I, therefore, use User.IsInRole(string role) but it always returns false.
In a similar question, I read that it's somehow not possible to retrieve the user-role right after login. However, I don't understand why that would be the case.
My code:
#if (User.IsInRole(Roles.Admin.ToString())) {
<li><a asp-page="/AdminMenuPoint">Admin Menu</a>a/li>
}
My roles enum:
public enum Roles {
Supervisor, Admin
};
To summarize: Why doesn't User.IsInRole() work for my hompage (after login)?
Thanks in advance.
If you use .Net Core you need to setup:
Add Identity Service in Startup.cs
Edited
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>() // <-- Add this line
.AddEntityFrameworkStores<ApplicationDbContext>();
According to this discussion on GitHub, getting the roles and claims to show up in the cookie involves either reverting to the service.AddIdentity initialization code, or sticking with service.AddDefaultIdentity and adding this line of code to ConfigureServices:
// Add Role claims to the User object
// See: https://github.com/aspnet/Identity/issues/1813#issuecomment-420066501
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
Create Role and Assign User for Role
private async Task CreateUserRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
IdentityResult roleResult;
//Adding Admin Role
var roleCheck = await RoleManager.RoleExistsAsync("Admin");
if (!roleCheck)
{
//create the roles and seed them to the database
roleResult = await RoleManager.CreateAsync(new IdentityRole("Admin"));
}
//Assign Admin role to the main User here we have given our newly registered
//login id for Admin management
ApplicationUser user = await UserManager.FindByEmailAsync("syedshanumcain#gmail.com");
var User = new ApplicationUser();
await UserManager.AddToRoleAsync(user, "Admin");
}
I'm using ASP.NET MVC Entity Framework.
In my application I'm currently able to login and retrieve some data, but I'm not sure if the way I do it is a secure way.
Here is how my application works:
Currently when a user logs in, I make use of the following to store the username in a cookie:
FormsAuthentication.SetAuthCookie(user.Username, true);
Then I have a RoleProvider class that contains the following method, which returns a string array with the users role:
public override string[] GetRolesForUser(string username)
{
List<string> rolesList = new List<string>();
string role = CurrentlyLoggedInUser.User.Role.Name;
rolesList.Add(role);
string[] rolesArray = rolesList.ToArray();
return rolesArray;
}
Then I have the following class, that stores data of the currently logged in user:
public class CurrentlyLoggedInUser
{
private const string UserKey = "MyWebApp.Infrastructure.UserKey";
public static User User
{
get
{
MyDBEntities db = new MyDBEntities();
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
return null;
}
var user = HttpContext.Current.Items[UserKey] as User; //we set the
if (user == null)
{
user = db.Users.FirstOrDefault(x => x.Username == HttpContext.Current.User.Identity.Name);
if (user == null)
{
return null;
}
HttpContext.Current.Items[UserKey] = user;
}
return user; //Then we return the user object
}
}
}
Then whenever I need to query the database to find any related data of the user, I retrieve the users Id as follow:
int id = CurrentlyLoggedInUser.User.UserId;
Now I can make queries using this Id.
When I'm done, I logout and call the following:
FormsAuthentication.SignOut();
Is this recommended and secure enough?
Consider requiring https to secure you login form, don't forget about Authorize attribute on your MVC controllers and/or actions (or, preferably set Authorize attribute as a global filter) and on the basic level that's enough. #Chad-Nedzlek is not absolutely correct, FormsAuthentication, you involve, takes care about your authentication cookie encryption, so it's not so easy for anyone to impersonate any other. But you have to take care about preventing bruteforce attacks when implementing your custom login: consider password lifetime limitation, required complexity etc.
And if you are thinking about future extensibility, SSO for web, mobile apps and API, or possibility of easy use of external (Facebook, Google etc) login, consider switching to OpenId Connect.
getting my head wrapped around the new Identity framework and am trying to figure out how best to handle custom user properties. I have tried extending the IdentityUser, which works to store the information, but so far is requiring an additional db call to get the property back out. I am looking at switching to using claims to store/retrieve this information.
First, the specific prop I want to store/retrieve is not unique to an individual user (many to one). Consider grouping users together in a custom Group structure. I want to store the GroupId for use in other related entities.
I am able to store the GroupId (currently using the ClaimTypes.NameIdentifier which I don't think it the correct usage for that type, but...). But, when I go to retrieve that value, the claim type isn't found in the claims collection. It's in the db, so I know it's there. I'm missing something.
FWIW: Since it's WebAPI, I'm not using a traditional sign-in. I'm using token auth.
When I create the user, I have something like:
public async Task<IdentityResult> CreateUserAsync(string email, string password, string groupId)
{
var userId = ObjectId.GenerateNewId(DateTime.UtcNow).ToString(); // yes, it's a NoSQL store
var user = new ApplicationUser
{
Id = userId,
UserName = email
};
var claim = new IdentityUserClaim { ClaimType = ClaimTypes.NameIdentifier, ClaimValue = groupId, UserId = userId, Id = ObjectId.GenerateNewId(DateTime.UtcNow).ToString() };
user.Claims.Add(claim);
var result = await _UserManager.CreateAsync(user, password);
return result;
}
That creates what looks to be an appropriate db entry.
When I retrieve the value, I get null reference errors. Here's that code via an extension method:
public static string GetGroupId(this IIdentity identity)
{
var claimsIdentity = identity as ClaimsIdentity;
return claimsIdentity == null ? "" : claimsIdentity.FindFirst(ClaimTypes.NameIdentifier).Value;
}
The error hits when trying to get Value as the FindFirst is returning a null value.
Any hints or better/best practices here would be appreciated! Honestly, I'd prefer to just store this on the ApplicationUser : IdentityUser object, but I can't find a simple way of retrieving that of User.Identity in my api controller context without an additional call to the db.
Your gut feeling about storing extra data as a claim is correct, but implementation is a bit broken.
I recommend to have your own claim types created for your domain information. Do not reuse claim types provided from framework. Reason for that is ClaimTypes.NameIdentifier represents User.Id.
The framework itself adds standard list of claims to all users:
User.Id => represented as ClaimTypes.NameIdentifier
Username => represented as 'ClaimTypes.Name'
ProviderName => represented as ClaimTypes.ProviderName (not 100% sure about this one); Usually value is "ASP.NET Identity"
SecurityStamp value (not sure what the claim type name for it)
All the roles assigned to the user are stored as ClaimTypes.Role
So in your case you have tried to overwrite claim with value of User.Id which is quite important, I would think -)
Now, let's try to fix your coding problems. When you create a user, you add claims after you have created a user object:
public async Task<IdentityResult> CreateUserAsync(string email, string password, string groupId)
{
var user = new ApplicationUser
{
Id = userId,
UserName = email
};
var userCreateResult = await _UserManager.CreateAsync(user, password);
if(!userCreateResult.IsSuccess)
{
// user creation have failed - need to stop the transaction
return userCreateResult;
}
// better to have a class with constants representing your claim types
var groupIdClaim = new Claim("MyApplication:GroupClaim", ObjectId.GenerateNewId(DateTime.UtcNow).ToString());
// this will save the claim into the database. Next time user logs in, it will be added to Principal.Identity
var claimAddingResult = await _UserManager.AddClaimAsync(userId, groupIdClaim);
return claimAddingResult;
}
As for extension methods I usually work with IPrincipal or ClaimsPrincipal. But IIdentity is also workable. Don't forget you can access ClaimsPrincipal anywhere by calling ClaimsPrincipal.Current.
This is how I usually work with extension methods:
public static string GetGroupId(this ClaimsPrincipal principal)
{
var groupIdClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApplication:GroupClaim");
if (personIdClaim != null)
{
return groupIdClaim.Value;
}
return String.Empty;
}
So in your methods you'd retrieve assigned groupId for the currently logged in user like this:
var groupId = ClaimsPrincipal.Current.GetGroupId();
Hope this clarifies your confusion!