How to create identity user based on claims prinicpals [duplicate] - c#

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);

Related

How can I retrieve a user from a password reset token in ASP.NET Core?

I have an ASP.NET Core 2.1 web application and am adding forgot password functionality. I have looked at several examples, and they seem to take one of two approaches. The first approach is to include either the user id or the user's email in the password reset url along with the password reset token. The second approach is to include only the password reset token in the password reset url and then require the user to enter identifying information (such as email) when attempting to change the password (Binary Intellect example). Is there a way to look up the user given just the password reset token?
My team lead has asked me to pass just the token in the password reset url and then look up the user. My initial research makes me believe that I would have to manually keep record of the user id and token relationship, but am hoping that there's something built in. I have reviewed the ASP.NET Core UserManager documentation, but did not find any methods for retrieving a user for a given token.
Here's some of the example code embedding the user id in the password reset URL (Microsoft Password Recovery Doc):
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
There is a way to get the UserId from the password reset token, but in my opinion it's tricky and a lot of work.
What are the defaults
If you have some codes like the following,
services.AddIdentity<AppUser, AppRole>(options =>
{
...
}
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
the last line .AddDefaultTokenProviders() adds 4 default token providers, which are used to generate tokens for reset passwords, change email and change phone number options, and for two factor authentication token generation, into the pipeline:
DataProtectorTokenProvider
PhoneNumberTokenProvider
EmailTokenProvider
AuthenticatorTokenProvider
The first one, DataProtectorTokenProvider, is what we're looking for. It uses data protection to serialize/encrypt those tokens.
And within the DataProtectorTokenProvider, its protector is default to the name of "DataProtectorTokenProvider".
How tokens are generated
If you look at GenerateAsync() method inside DataProtectorTokenProvider, you can kind of tell the token consists of:
Utc timestamp of the token creation (DateTimeOffset.UtcNow)
userId
Purpose string
Security stamp, if supported
The generate method concatenates all those, transform them to a byte array, and calls the protector inside to protect/encrypt the payload. Finally the payload is converted to a base 64 string.
How to get User Id
To get the userId from a token, you need to do the reverse engineering:
Convert the token from base 64 string back to the byte array
Call the protector inside to unprotect/decrypt the byte array
Read off the Utc timestamp
Read userId
The tricky part here is how to get the same DataProtector used to generate those token!
How to get the default Data Protector
Since the default DataProtectorTokenProvider is DIed into the pipeline, the only way I can think of to get the same DataProtector is to use the default DataProtectorTokenProvider to create a protector with the same default name, "DataProtectorTokenProvider", used to generate tokens!
public class GetResetPasswordViewModelHandler : IRequestHandler<...>
{
...
private readonly IDataProtector _dataProtector;
public GetResetPasswordViewModelHandler(...,
IDataProtectionProvider dataProtectionProvider)
{
...
_dataProtector = dataProtectionProvider.CreateProtector("DataProtectorTokenProvider");
// OR
// dataProtectionProvider.CreateProtector(new DataProtectionTokenProviderOptions().Name);
}
public async Task<ResetPasswordViewModel> Handle(GetResetPasswordViewModel query, ...)
{
// The password reset token comes from query.ResetToken
var resetTokenArray = Convert.FromBase64String(query.ResetToken);
var unprotectedResetTokenArray = _dataProtector.Unprotect(resetTokenArray);
using (var ms = new MemoryStream(unprotectedResetTokenArray))
{
using (var reader = new BinaryReader(ms))
{
// Read off the creation UTC timestamp
reader.ReadInt64();
// Then you can read the userId!
var userId = reader.ReadString();
...
}
}
...
}
}
Screenshot:
My 2 cents
It seems like it's a lot of work just try to read the userId off a password reset token. I understand your team lead probably doesn't want to expose the user id on the password reset link, or (s)he thinks it's redundant since the reset token has the userId.
If you're using integer to represent the userId and don't want to expose that to public, I would change it to GUID.
If you have to use integer as your userId, I would just create a column of the type unique_identifier off the user profile (I would call it PublicToken) and use that to identifier a user for all public matters.
var callbackUrl = Url.Action("resetPassword", "account", new
{
area = "",
rt = passwordResetToken, // reset token
ut = appUser.Id // user token, use GUID user id or appUser.PublicToken
}, protocol: Request.Scheme);
I believe there is no way you can do that you can pass user email then find it look for user in your code
public async Task<IActionResult> ResetPassword([FromBody]ResetPasswordViewModel model)
{
if (string.IsNullOrEmpty(model.Token) || string.IsNullOrEmpty(model.Email))
{
return RedirectToAction("Index", "Error", new { statusCode = AppStatusCode.NotFound });
}
var isResetTokenValid = await _userManager.CheckValidResetPasswordToken(model.Token, model.Email);
if (!isResetTokenValid || string.IsNullOrEmpty(model.Email))
{
return StatusCode(AppStatusCode.ResetPassTokenExpire);
}
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
return Ok();
}
await _userManager.ResetPasswordAsync(user, model.Token, model.Password);
return Ok();
}
You can view the implementaion detail here
What I do in this case is I keep that new token in a cache or sql table with user id in it. That way you first query that table containing reset token, validate it if you need it and get user.

Updated value of Claim is not reflecting in the subsequent requests

In my project, I am using token-based authentication and after a successful login, I store some user-specific values in user's token and to do this I have used Claims.
Below is the code I am using to store claims after login:
User user = new UserManager().GetUser(UserName, Password);
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, user.FullName),
new Claim(ClaimTypes.Email, user.Email),
new Claim("IsLocked", Convert.ToString(user.IsLocked))
};
AuthenticationProperties properties = CreateProperties(context.UserName);
ClaimsIdentity oAuthIdentity = new ClaimsIdentity(claims, Startup.OAuthOptions.AuthenticationType);
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
As you can see in the above code that I have a claim to store IsLocked value of the user. As per the requirement, I need to prevent access of each API action from users whose account is locked. To do so, I have created a custom action filter and inside that, I use the value of IsLocked claim and thus prevent actions from being executed if user's claim value says that the user account is locked.
Below is the code of my custom action filter:
public class AllowActiveUsersAttribute : ActionFilterAttribute
{
public AllowActiveUsersAttribute()
{
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var identity = (ClaimsPrincipal)Thread.CurrentPrincipal;
if (Convert.ToBoolean(identity.Claims.Where(c => c.Type == "IsLocked").Select(c => c.Value).SingleOrDefault()))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
And then I use this custom Attribute on all Web API actions, like:
[AllowActiveUsers]
public async Task<IHttpActionResult> GetAccountDetails()
This code works perfectly and I get Unauthorized error when I log in with a locked account and then try to use any API endpoint.
In our system, we have some rules and breaking those rules can lock the users' account. When the account gets locked then the user shouldn't be able to access any API endpoint. So after the successful login (with an account that is not locked), if a user breaks any rule then his/her account should get locked immediately and after that he/she must not be able to use any API endpoints.
To do this, I added code to update the value of IsLocked claim and it successfully updates the claim value. But when I try to get the value of IsLocked claim in the custom action then I get the same old value instead of the new return value. Below is the code that I am using to update the claim value.
// check for the existing claim and remove it
var user = User as ClaimsPrincipal;
var identity = user.Identity as ClaimsIdentity;
var claim = (from c in user.Claims where c.Type == "IsLocked" select c).FirstOrDefault();
if (claim != null)
identity.RemoveClaim(claim);
// add new claim
identity.AddClaim(new Claim("IsLocked", Convert.ToString(true)));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
Can you please suggest how I can get the new value in the custom attribute so that if a user's account gets locked then from next requests, none of the API requests should be entertained?
That's cause you are still using the old/existing token which still don't have the updated value of IsLocked and thus the scenario. To resolve this, either the user has to start afresh by means of generating a new token which would have the updated value. Essentially a new access token has to be generated.
See this issue link for more understanding https://github.com/IdentityServer/IdentityServer3/issues/2783

Custom UserValidator Affects Newly Created User Added to a Role

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.

How to Group Claims to be Used for AspNetUserClaims?

I am reading this book and it talks about using claims and roles are for more legacy. One thing it does not seem to talk about is how to store these claims.
Say I have these claims.
canRead
canWrite
CanUpdate
CanDelete
Now I have 2 types of "roles" admin(should have all these claims) and user(should just have canRead).
Should I make a new table(or hijack the roles tables...which from book seems like if you use AspNetUserClaims you won't use the role tables) in my database that stores these claims? Or in my service layer?
For instance a new user is being created and in my front end, I want to give the user a choice to make this person and "admin" or "user". Should I have something like this
public Claim {
public string ClaimType { get; set; }
public string ClaimValue { get; set; }
}
//somewhere in a service file.
List<Claim> allClaims = new List<Claim>(){
new Claim() {
ClaimType: "Permission",
ClaimValue: "canRead",
},
new Claim() {
ClaimType: "Permission",
ClaimValue: "canWrite",
},
new Claim() {
ClaimType: "Permission",
ClaimValue: "canUpdate",
},
new Claim() {
ClaimType: "Permission",
ClaimValue: "canDelete",
}
}
var groupedClaims = Dictionary<string,List<Claim>>()
groupedClaims.add("admin", allClaims);
groupedClaims.add("user", [only some of the claims]);
// then when need to create new user grab right group claims and insert into AspNetUserClaims
You're still thinking of roles, so you're trying to group the claims in groups that are like roles. You don't need to do that, although there is nothing wrong with it if it satisfies your project's requirements.
Without the roles (or groups), you would have a table for the user's claims. When the admin adds a new user to the system, they assign the claims (or permissions) to the user directly one by one.
With the roles (or groups), you have two choices.
1) Virtual roles: In this case, no need for any additional data table. When the admin adds a new user to the system, they assign the role(s) to the user. The system will then add the claims associated with each role to the user. So you're using the roles just as an easy way to assign groups of claims to the users without the admin having to click one by one.
2) Real roles: In this case, you need an additional data table for the user's role(s). When the admin adds a new user to the system, they assign the role(s) to the user. When the user logs in, the system will then load the claims associated with each user role.

Using OWIN Identity v2 Claims to track custom properties in WebAPI2 app

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!

Categories