ASP.NET core app authenticating with AAD: it is possible to retrieve additional user properties/attributes as claims? - asp.net-core

I have created an ASP.NET Core application that successfully authenticates users with Azure AD and have seen that for an authenticated user an instance of type System.Security.Claims.ClaimsPrincipal is returned containing a single identity of type System.Security.Claims.ClaimsIdentity with a Type property valued "preferred_username" that carries the user's UPN. Among the claims that are returned there is for example one with its Type property valued "name" which seems to contain the user's display name. I was wondering if there is a way to have other users' attributes/properties also returned in the form of claims.

I was wondering if there is a way to have other user's
attributes/properties also returned in the form of claims.
Obviously you can retrieve optional claims within your token using azure active directory. To achieve that you would need few settings in Token configuration under azure active directory blade.
What Additional Claim can be added:
Currently, you can include these optional claims in your both idToken, accessToken and saml2Token
Settings For Optional Claims:
If you would like to include additional claims within your token you ought to configure as following:
Select Token configuration in your app:
Select Add optional claim and Include your claims:
You should have profile access permission:
Check your Manifest if the claims added accordingly:
Now You can Call Optional Claims which are available ClaimTypes Class:
Note: You can check the available claims fields witin ClaimTypes Class here.
Code Snippet:
You can implement within your application as following:
var username = HttpContext.User.Identity.Name;
var givenName = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
var email = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var country = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Country)?.Value;
Note: If you would like to know more details on optional claims you could check our official document here.

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.

Retrieving client name in IdentityServer4 and ASP.NET Core

The dbo.Clients table in the IdentityServer database contains both ClientId and ClientName, however requesting a client credentials token doesn't include the client name in the token.
Is there a way to either retrieve client information from IdentityServer given the client id, or request that the client name be added to the token?
You can add them dynamically using a custom token request validator :
public class ClaimClientsUpdated : ICustomTokenRequestValidator
{
public Task ValidateAsync(CustomTokenRequestValidationContext context)
{
context.Result.ValidatedRequest.Client.AlwaysSendClientClaims = true;
context.Result.ValidatedRequest.ClientClaims.Add(new Claim("name", context.Result.ValidatedRequest.Client.ClientName));
return Task.FromResult(0);
}
}
Register in DI :
services.AddTransient<ICustomTokenRequestValidator, ClaimClientsUpdated>();
It will add prefix "client_" to custom claims , so claim will be "client_name": "value" in access token .
You can add information about the client in general by adding claims to the ClientClaims table. E.g. Type = Name, Value = MyCustomName
Which is added as claim (assuming prefix client_):
"client_Name": "MyCustomName"
This will allow you to add information about the client without having to add or change code.
The drawback for the client name is that you'll have to add a claim with redundant information, as Clients.ClientName is not the source. The advantage is that it's configuration only.

Use UserManager GenerateUserTokenAsync to create custom tokens with extra data

I am using ASP.NET Core 2.2 and I need to generate custom tokens in my application.
Asp.Net Core Identity UserManager can generate classic tokens like EmailVerification, ...
But it has also a method to generate tokens with different purposes (MSFT Docs):
public virtual System.Threading.Tasks.Task<string> GenerateUserTokenAsync (TUser user, string tokenProvider, string purpose);
I need to generate a token with the following information:
Purpose = AddUserToProject
User = A given user
ProjectId = The Project Id to which the User should be added.
RoleId = The Role Id of the User in the Project
On GenerateUserTokenAsync I can add the User and the Purpose ...
But I'm not sure how to add (3) and (4) e.g. ProjectId and RoleId.
And how can I retrieve it later so I can actually perform the action.
How should I do this?
You can create a custom token provider, and then instruct ASP.NET Core to use that. Create a class that implements IUserTokenProvider<TUser, TKey>. Then, you can either explicitly use it:
var token = await _userManager.GenerateUserTokenAsync(user, "MyCustomTokenProvider", purpose);
Or you can sub it in for any or all of the Identity token providers in ConfigureServices:
services.AddIdentity<IdentityUser, IdentityRole>(o => {
o.Tokens.PasswordResetTokenProvider = nameof(MyCustomTokenProvider);
// rinse and repeat for other token providers you want to change
// see: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration?view=aspnetcore-2.2#tokens
})
.AddTokenProvider<MyCustomTokenProvider>(nameof(MyCustomTokenProvider));
[1]:

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

Adding more user information to ASP.NET Default Membership Provider

My application is an MVC4 application with a Domain Model created in EF 5 Code First. The application requires Authentication / Authorization, and I want to use the default ASP.NET Membership Provider.
With this in mind, I have gone ahead and used the aspnet_reqsql utility to add all the tables necessary for the ASP.NET Default Membership provider.
However, my application needs to store more information about the User than what is provided by default by the Membership provider. For example:
First Name
Last Name
Date of Birth
Address (split into different
columns)
These things are not present in the membership provider tables. So I went ahead and added all the missing columns to the users table, and also created an Addresses table, and created a relationship between the User and the Address.
I then went into my Registration View Model, and added the missing data fields, I then went into the AccountController and checked the method that gets called to register a user. It is this:
//
// Summary:
// Creates a new user profile entry and a new membership account.
//
// Parameters:
// userName:
// The user name.
//
// password:
// The password for the user.
//
// propertyValues:
// (Optional) A dictionary that contains additional user attributes. The default
// is null.
//
// requireConfirmationToken:
// (Optional) true to specify that the user account must be confirmed; otherwise,
// false. The default is false.
//
// Returns:
// A token that can be sent to the user to confirm the user account.
//
// Exceptions:
// System.InvalidOperationException:
// The WebMatrix.WebData.SimpleMembershipProvider.Initialize(System.String,System.Collections.Specialized.NameValueCollection)
// method was not called.-or-The Overload:WebMatrix.WebData.WebSecurity.InitializeDatabaseConnection
// method was not called.-or-The WebMatrix.WebData.SimpleMembershipProvider
// membership provider is not registered in the configuration of your site.
// For more information, contact your site's system administrator.
public static string CreateUserAndAccount(string userName, string password, object propertyValues = null, bool requireConfirmationToken = false);
This method is confusing me a lot ! I was expecting to see the logic of data insertion into the database, so that I may edit it and add make the method take care of my newly added fields too, but all that missing!
What am I missing? How can I achieve the type of registration that I want?
First of all, you want to use new ASP.NET Universal Providers which uses Entity Framework.
If you want to add custom columns, create a new table like the following, and retrieves that custom data based on UserId by yourself.
Note: You cannot alter (add/remove) columns of any table created by Membership Provider, if you want to use DefaultMembershipProvider.
In other words, if you start adding columns, you'll have to implement CustomMembersipProvider. I do not recommend it if you are new to MembershipProvider.