AddToRoleAsync fails to find Role - asp.net-core

I have a small test app using Asp.net Core Identity.
In the startup I check that certain system roles are in place:
if(await _roleManager.FindByNameAsync(“SYSADMIN”) == null)
{
_context.Roles.Add(new IdentityRole(“SYSADMIN”));
await _context.SaveChangesAsync();
}
Then I check and create a system admin account if it doesn’t exist:
var result = await _userManager.CreateAsync(adminUser, config["AdminPassword"]);
I then try and add that user to the SYSADMIN role:
if (result == IdentityResult.Success)
{
await _userManager.AddToRoleAsync(adminUser, “SYSADMIN”);
}
but get an error that the role does not exist. I can, however, see the role with the above name in AspNetRoles and when I run the app for a second time, it doesn’t go into the _context.Roles.Add() section as _roleManager.FindByNameAsync returns the role.
Has anyone seen this behaviour before or know whats going on as to why its failing?
edit
I notice NormalisedName is null though in the DB - is that what it is using to match?

So this looks like a bug to me. If you use the constructor that takes only a string, it populates the name, but not the normalised name. It would appear that the normalised name is matched on in AddToRoleAsync so it'll never work.
I needed to use the following to force the NormalizedName to be populated:
_context.Roles.Add(new IdentityRole("SYSADMIN")
{
NormalizedName = "SYSADMIN"
});
And its now working.
I'll file a bug with the team and hopefully it'll get fixed.

Related

Adding and accessing claims in asp net core 3.0 using built in Identity server

I'm currently failing at wrapping my head around claims. I have a ASP.Net Core 3 project with the angular template and users stored in app.
I want to add claims to my users, reading up on I thought it would be easy, just add something along the lines of
await _UserManager.AddClaimAsync(user, new Claim(AccountStatic.ClaimTypes._Claim_Id, user.Id));
When you create the user, and then get it back using the below line once they are logged in again:
User.FindFirst(AccountStatic.ClaimTypes._Claim_Id)?.Value;
This does however not work. I can see the claims being written to AspNetUserClaims table in my database but it's not there in the users claims when they log in. There are a few other claims there, but not the ones I have added.
Do I need to define somewhere which of the users claims get included when they log in?
Edit.
I found a post stating that I need to add claims using a DI AddClaimsPrincipalFactory. So I added this class.
public class UserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public UserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager,IOptions<IdentityOptions> optionsAccessor): base(userManager, optionsAccessor)
{}
//https://levelup.gitconnected.com/add-extra-user-claims-in-asp-net-core-web-applications-1f28c98c9ec6
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
identity.AddClaim(new Claim(AccountStatic.ClaimTypes.Claim_Id, user.Id ?? "[no id]"));
return identity;
}
}
And if I step through the code I can see the claims being added here. But in the Controller my custom claims are not present.
internal string GetUserId()
{
if (User.Identity.IsAuthenticated == false)
return null;
return User.FindFirst(AccountStatic.ClaimTypes.Claim_Id)?.Value;
}
Update. Ok I find this very strange. I have been trying to do what others claim work but for me nothing gets me the users name or id. inspecting the User I get the following. Nothing here contains any reference to the logged in user.
Update 2:
Just noticed that there is actually an Id in there: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: ed107a11-6c62-496b-901e-ed9e6497662a} Seems to be the users id from the database. Not sure how to access it yet though.
These return null.
User.FindFirst(JwtRegisteredClaimNames.NameId)?.Value;
User.FindFirst("nameidentifier")?.Value;
User.FindFirst("NameIdentifier")?.Value;
Another update
I'm using a UserClaimsPrincipalFactory and breakingpointing it and looking at the Claims I can see that all of the ones I want are there. But again, these are not available in my API controllers as seen in the first picture.
I finally understood the problem, in large parts thanks to Ruard van Elburgs comments, and the answer he made in the linked question IdentityServer4 Role Based Authorization.
The problem is that the claims are not added to the access token.
There are two tokens, the access token and the identity token.
- Ruard van Elburg
They key to understanding what was going on was finding out that there are two tokens, and that they contain different claims and have different purposes.
You can force claims from one token to also be included in the other if you deem it necessary.
The solution to my problem was to add this in Startup.ConfigureServices
services
.AddIdentityServer(options => {})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
foreach (var c in options.ApiResources)
{
// the string name of the token I want to include
c.UserClaims.Add(AccountStatic.ClaimTypes.Claim_Id);
}
});
I still have not figured out how to get the Identity token, but as I'm now including the user Id in the access token my problems are solved for the moment.

In JWT Authorization, check if user has role Admin

I am working on a .Net Core API, and inside my Controller, I have the following code:
if (User.Identity.IsAuthenticated)
{
var username = HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByNameAsync(username);
artistCardDtoCollection = _artistsService.GetAllArtists(user.Id, User.IsInRole("Admin"));
}
The code above is because I wish to pass the User.Id (if logged in) and a IsAdmin flag to my GetAllArtists method.
The code above is failing on User.IsInRole("Admin"). I get a false when I know 100% that the user in question is an Admin. I've double checked the database via SQL Management Studio.
This makes me think one can't use User.IsInRole() when working with JWT. If that is the case, then what is the correct way? Thanks
Probably it could be the caching issue with User.IsInRole(), if we check documentation we will find:
IsInRole first checks the IsRoleListCached property to determine
whether a cached list of role names for the current user is available.
If the IsRoleListCached property is true, the cached list is checked
for the specified role. If the IsInRole method finds the specified
role in the cached list, it returns true. If IsInRole does not find
the specified role, it calls the GetRolesForUser method of the default
Provider instance to determine whether the user name is associated
with a role from the data source for the configured ApplicationName
value.
In your case you can try to use GetRolesAsync like below:
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
artistCardDtoCollection = _artistsService.GetAllArtists(user.Id, roles.Contains("Admin"));

Web API 2 cannot register user

I have a Web API 2 project using MVC. It uses entity framework. This entity framework uses a database first approach with a .edmx file.
The project is based on VS 2013 Express Web API 2 template. I just used my own database. I didn't modify any account related code. But when I try to register a new user, the following statement in AccountController.cs throw exception:
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
...
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
...
}
The exception says:
Cannot insert the value NULL into column 'Discriminator', table 'xxx.dbo.AspNetUsers'; column does not allow nulls. INSERT fails.
The statement has been terminated.
Can anyone help me? Thank you!
The answer is in your question's body.
table 'xxx.dbo.AspNetUsers'; column does not allow nulls.
Seems to be, that you're trying to insert a NULL value from your model instance RegisterBindingModel model, where the structure of your table in database server doesn't allow to accept NULL value.
Try to debug and check, is your model instance is creating correctly when the request is coming.
Also I can see, that you use:
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
Why user isn't a member of your model, like the Password property?
Maybe try to use:
IdentityResult result = await UserManager.CreateAsync(model.User, model.Password);
Anyway... I don't have more info and don't imagine, what is inside your model instance, how you're binding data in it from the request. You should to look in the debugger and debug very well to repair your project or provide more info.
But one thing is clear as a crystal, you're trying to insert a NULL value into the column, which doesn't accept NULL, so you must check how is your model binding. Maybe your client-side application doesn't send correctly some arguments, maybe you're binding you model incorrectly, maybe something else... There is a need to get more information from you to help you, otherwise you should debug carefully.
I upgraded all NuGet Packages yesterday. Among them Asp.net Identity is upgraded to 2.0.0.0.
It magically worked.
So I suspect that was because of some bug in Identity 1.0

Accessing Meteor application as another user

I've recently updated some parts of the code and want to check if they play well with production database, which has different data sets for different users. But I can only access the application as my own user.
How to see the Meteor application through the eyes of another user?
UPDATE: The best way to do this is to use a method
Server side
Meteor.methods({
logmein: function(user_id_to_log_in_as) {
this.setUserId(user_id_to_log_in_as);
}
}):
Client side
Meteor.call("logmein", "<some user_id of who you want to be>");
This is kept simple for sake of clarity, feel free to place in your own security measures.
I wrote a blog post about it. But here are the details:
On the server. Add a method that only an admin can call that would change the currently logged user programatically:
Meteor.methods(
"switchUser": (username) ->
user = Meteor.users.findOne("username": username)
if user
idUser = user["_id"]
this.setUserId(idUser)
return idUser
)
On the client. Call this method with the desired username and override the user on the client as well:
Meteor.call("switchUser", "usernameNew", function(idUser) {
Meteor.userId = function() { return idUser;};
});
Refresh client to undo.
This may not be a very elegant solution but it does the trick.
Slightly updated answer from the accepted to log the client in as new user as well as on the server.
logmein: function(user_id_to_log_in_as) {
if (Meteor.isServer) {
this.setUserId(user_id_to_log_in_as);
}
if (Meteor.isClient) {
Meteor.connection.setUserId(user_id_to_log_in_as);
}
},
More info here: http://docs.meteor.com/api/methods.html#DDPCommon-MethodInvocation-setUserId

Login as user without password (For an Admin Use-Case.)

To check if the view of a user is working or to make change out of the users view point (in development) it can be quite useful to incarnate a certain user.
How would I do this with Meteor? Best would be a solution which is independent of the Account Authentication.
To impersonate a user in production, you can call setUserId on the server, and Meteor.connection.setUserId on the client. For more details, see my blog post.
If you're using Meteor.userId() and Meteor.user() to identify your person in your javascript you could use something like this to override it at the very top of your client js
Meteor.userId = function (impersonate_id) {
return (impersonate_id) ? impersonate_id : Meteor.default_connection.userId();
}
Meteor.user = function (impersonate_id) {
var userId = Meteor.userId(impersonate_id);
if (!userId)
return null;
return Meteor.users.findOne(userId);
}
And now when you use Meteor.userId or Meteor.user modify your code so everywhere you use Meteor.user & Meteor.userId accepts an argument. So when you want to impersonate a user just pass it argument of the _id of the user you want to log in as
Meteor.user("1"); //Loads the data for user with _id 1
Meteor.user(); //Loads the actual logged in user
Also this will only work if you're actually the admin and your publish function allows you to see all your user's data