userManager.FindByName does not return roles - asp.net-core

I am using OpenIddict for token authentication. Yesterday when I call userManager.FindByNameAsync(request.Username) I get User with Roles.
Today I get user with Roles property count = 0.
I tried to load roles with await userManager.GetRolesAsync(user); and I get array with count 3. That means user has roles.
I do not know what changed, but how can I load user with roles with FindByNameAsync function?
Complete code:
[HttpPost("token"), Produces("application/json")]
public async Task<IActionResult> Exchange(OpenIdConnectRequest request)
{
Debug.Assert(request.IsTokenRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (request.IsPasswordGrantType())
{
var user = await userManager.FindByNameAsync(request.Username); //roles count is 0
if (user == null)
{
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The email/password couple is invalid."
});
}
var roles = await userManager.GetRolesAsync(user); //roles count is 3

but how can I load user with roles with FindByNameAsync function?
You can't (at least not without implementing your own IUserStore).
Under the hood, the default ASP.NET Core Identity store implementation (based on EF) doesn't eagerly load the navigation properties associated with the user entity for performance reasons, which is why the Roles property is not populated.
To load the roles associated with a user, use UserManager.GetRolesAsync(user);.

Related

How to handle array claim values in ASP.net Core using OIDC

Im running Skorubas implementation of IdentityServer4
https://github.com/skoruba/IdentityServer4.Admin
For some reason I end up receiving a single role-claim with the claim-type "role" and a value of an array with all roles for the current user:
["SkorubaIdentityAdminAdministrator","MyRole"]
Now if I would to protect a "page" using the Authorize-attribute:
[Authorize(Role="MyRole")]
This would always end up with an access denied, since ASP.net Core expects multiple claims with the same claim-type, so in this case the claims would be
type | value
role:"SkorubaIdentityAdminAdministrator"
role:"MyRole"
is there any "best practice" to either parse the claims received and reformat them before they are processed by ASP.net core, or to tell the OpenIdConnect extension to handle the array-format as multiple claims?
Generally claims received in JWTs can be arrays or objects as well as simple types. The way to deal with this when using .NET attributes for authorization is via policies.
They are pretty simple and this Curity tutorial has some examples. This code snippet shows that the entire ClaimsPrincipal is available to policies so you could work with array claims easily in your use case:
options.AddPolicy("lowRisk", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(claim =>
claim.Type == "risk" && Int32.Parse(claim.Value) < 50
)
)
);
[HttpGet("lowrisk")]
[Authorize( Policy = "lowRisk")]
public IActionResult LowRisk()
{
return Ok();
}
Turns out that you can create your own ClaimActions, in the example above I had to do the following:
Firts of all.. create a new class:
public class RoleClaimAction : ClaimAction
{
private const string RoleClaimType = "role";
public RoleClaimAction() : base(RoleClaimType, ClaimValueTypes.String)
{
}
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
//Map array of roles to separate role claims
var roles = userData.TryGetStringArray(RoleClaimType)?.ToList();
if (roles!.Any())
{
foreach (var role in roles!)
{
AddRoleClaim(identity, role, issuer);
}
return;
}
//If we only have one role (not an array), add it as a single role claim
var singleRole = userData.TryGetString(RoleClaimType);
if(!string.IsNullOrEmpty(singleRole))
AddRoleClaim(identity, singleRole, issuer);
}
private void AddRoleClaim(ClaimsIdentity identity, string role, string issuer)
{
identity.AddClaim(new Claim(JwtClaimTypes.Role, role, ClaimValueTypes.String, issuer));
}
}
This will simply validate that the user has a claim called roles, and the re-map the array-values to separate role-claims, which then "hooks" into the auth-framework.
To add your ClaimAction, simply add it as the following to your OpenIdConnectOptions:
options.ClaimActions.Add(new RoleClaimAction())
Now Authorize-attributes with roles, and the User.IsInRole(string) should work properly.

How to extend and validate session in ASP.NET Core Identity?

We want to offer the users to manage their login sessions.
This worked so far pretty easy with ASP.NET Core and WITHOUT the Identity Extensions.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes
But how can we invoke this validation with ASP.NET Core Identity?
Problem we have:
How do we store login-session-based information like Browser Version, Device Type and User Position? Do we extend any type or what is the idea?
How do we dynamically set the cookie expiration based on a specific user?
How do we invalidate the Cookie from the backend (like the link above shows)?
How do we required additional password-prompts for special functions?
It feels the ASP.NET Core Identity is still not that extensible and flexible :(
Unfortunately, this area of ASP.NET Identity is not very well documented, which I personally see as a risk for such a sensitive area.
After I've been more involved with the source code, the solution seems to be to use the SignIn process of the SignIn Manager.
The basic problem is that it's not that easy to get your custom claims into the ClaimsIdentity of the cookie. There is no method for that.
The values for this must under no circumstances be stored in the claims of the user in the database, as otherwise every login receives these claims - would be bad.
So I created my own method, which first searches for the user in the database and then uses the existing methods of the SignInManager.
After having a ClaimsIdentity created by the SignIn Manager, you can enrich the Identity with your own claims.
For this I save the login session with a Guid in the database and carry the id as a claim in the cookie.
public async Task<SignInResult> SignInUserAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
DateTimeOffset createdLoginOn = DateTimeOffset.UtcNow;
DateTimeOffset validTo = createdLoginOn.AddSeconds(_userAuthOptions.ExpireTimeSeconds);
// search for user
var user = await _userManager.FindByNameAsync(userName);
if (user is null) { return SignInResult.Failed; }
// CheckPasswordSignInAsync checks if user is allowed to sign in and if user is locked
// also it checks and counts the failed login attempts
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
if (attempt.Succeeded)
{
// TODO: Check 2FA here
// create a unique login entry in the backend
string browserAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"];
Guid loginId = await _eventDispatcher.Send(new AddUserLoginCommand(user.Id, user.UserName, createdLoginOn, validTo, browserAgent));
// Write the login id in the login claim, so we identify the login context
Claim[] customClaims = { new Claim(CustomUserClaims.UserLoginSessionId, loginId.ToString()) };
// Signin User
await SignInWithClaimsAsync(user, isPersistent, customClaims);
return SignInResult.Success;
}
return attempt;
}
With each request I can validate the ClaimsIdentity and search for the login id.
public class CookieSessionValidationHandler : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
ClaimsPrincipal userPrincipal = context.Principal;
if (!userPrincipal.TryGetUserSessionInfo(out int userId, out Guid sessionId))
{
// session format seems to be invalid
context.RejectPrincipal();
}
else
{
IEventDispatcher eventDispatcher = context.HttpContext.RequestServices.GetRequiredService<IEventDispatcher>();
bool succeeded = await eventDispatcher.Send(new UserLoginUpdateLoginSessionCommand(userId, sessionId));
if (!succeeded)
{
// session expired or was killed
context.RejectPrincipal();
}
}
}
}
See also
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes

How to add and Persist a new ClaimsIdentity with custom claims to a ClaimsPrincipal after authentication by an OpenId Connect provider

I'm using OpenId Connect with .NetCore to send users to an external OpenId Connect authentication provider (OP) which returns relevant access tokens and an authenticated ClaimsPrinciple which has claims from the OP in such as Name, Email Address, Customer Id, etc. What I want to be able to do is once the User has been authenticated and the ClaimsPrinciple is returned from the OP, I would like to add a ClaimsIdentities with custom claims for each licence the user holds on there account to the ClaimsPrinciple. So when a user switches between their account licences I can access the correct identity for that licence and provide access to features based on the custom claims. Currently, I can add the custom claims to a ClaimsIdentity then add the ClaimsIdentity to the ClaimsPrinciple but the new identities are not persisted and added into the Cookie.
So A User can have multiple licences.
For each licence I want to add a ClaimsIdentity.
Then I want to persist the changes to the ClaimsPrinciple by using a cookie.
Here is a code snippet which will hopefully add some context. This is for the Login method in my application which is hit once the OP has authenticated the user.
public async Task<IActionResult> Login()
{
string token = HttpContext.Authentication.GetTokenAsync("access_token").Result;
string refreshToken = HttpContext.Authentication.GetTokenAsync("refresh_token").Result;
// ControllerBase User class this is the User I want to add the identites to, I think.
string userGuid = User.Claims.Where(c => c.Type == "Guid").FirstOrDefault().Value;
UserTokens userTokens = new UserTokens
{
LastUpdatedDate = DateTime.Now,
UserGuid = userGuid,
UserAccessToken = token,
RefreshToken = refreshToken
};
await _busClient.PublishAsync<UpdateUserTokens>(new UpdateUserTokens(userTokens));
// Domain Model User, a different User to the controllerBase User
// this is how a user is represented in my application but this has
// no control over authentication and claims
User user = await _requestClient.RequestAsync<UserGuidRequest, User>(new UserGuidRequest(userGuid));
var userLicences = await _requestClient.RequestAsync<UserLicenceRequest, List<UserLicence>>(new UserLicenceRequest(userGuid));
var identityServerUserClaims = User.Claims.ToList();
foreach(UserLicence userLicence in userLicences)
{
List<Claim> userLicenceIdentityClaims = new List<Claim>();
foreach (Claim claim in identityServerUserClaims)
{
userLicenceIdentityClaims.Add(claim);
}
userLicenceIdentityClaims.Add(new Claim(userLicence.RoleType.ToString(), ""));
var userLicenceIdentity = new ClaimsIdentity(userLicenceIdentityClaims, User.Identity.AuthenticationType);
userLicenceIdentity.Label = userLicence.Id.ToString();
User.AddIdentity(userLicenceIdentity);
}
//TODO: either need to sign out then back in or save new user claims to cookies somehow?
return View("Index", user);
}
If anyone could help it would be greatly appreciated. If you need any more information please ask and I'll try to provide whatever I can, hopefully, this is a good start.

Claims based authorization with ASP.NET MVC

I was reading up a lot of blog posts and stackoverflow answers but still I am unable to find a real world open source project which uses claims based authentication and authorization, so that I can get an idea on how to actually implement these.
So far what I could find is Thinktecture.IdentityModel and this blog implements a claims based authorization on a sample website. If you guys could point me some Open source projects using claims, that would be really helpful.
What I am interested is how to retrieve claims for my application using the database.
So far, what I have tried is that using an in memory claims store to simulate the databsae, I have created a CustomClaimsTransformer and CustomAuthorisationManager like this.
public class CustomClaimsTransformer : ClaimsAuthenticationManager
{
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
//validate name claim
string nameClaimValue = incomingPrincipal.Identity.Name;
return CreatePrincipal(nameClaimValue);
}
private ClaimsPrincipal CreatePrincipal(string userName)
{
int userId = ClaimStore.Users.First(u => u.Value == userName).Key;
var claims = ClaimStore.ClaimsSet.Where(c => c.Key == userId);
var claimsCollection = claims.Select(kp => kp.Value).ToList();
return new ClaimsPrincipal(new ClaimsIdentity(claimsCollection, "Custom"));
}
}
public class CustomAuthorisationManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
string resource = context.Resource.First().Value;
string action = context.Action.First().Value;
if (action == "Show" && resource == "Code")
{
bool likesJava = context.Principal.HasClaim(ClaimStore._httpMyclaimsUsers, "True");
return likesJava;
}
else if (action == "Read" && resource == "Departments")
{
bool readDeps = context.Principal.HasClaim(ClaimStore._httpMyclaimsDepartments, "Read");
return readDeps;
}
return false;
}
}
How to implement these in a real world scenario without having too many IF conditions?
Try the following link , it seems like a decent solution
http://developers.axiomatics.com/blog/index/entry/custom-claims-based-authorization-in-net-using-axiomatics-pep-sdk-for-net.html
Also you can define your policy and load it
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthorizationmanager.loadcustomconfiguration.aspx
How to: Implement Claims Authorization in a Claims-Aware ASP.NET Application Using WIF and ACS
http://msdn.microsoft.com/en-us/library/gg185907.aspx
I finally managed to design my own system with the required functionality using the existing asp.net identity 2.0 tables + a few of my own.
I'm gonna call every AREA-CONTROLLER-ACTION trio as resources in my system. WebAPI included. Area itself is a resource. Controller itself is a resource. Action itself is a resource. Any combination of them, is also a resource. I'll auto generate everything from the system itself using reflection.
Also, I'm going to use the same AspNetRoles table to store my User Groups. Users belong to one or more groups (Super Admin, Admin, Agent, Client etc.).
Using the existing Role based model as a user group based model with claims, I could get it working.Super admins are on god mode. They can create lower level users/groups/assign permissions etc.
Users can have special permissions. For example, Everyone in Agent group is denied access to updating a hotel, but a special agent who might also be the owner of a hotel can be given specific access to updating only their hotel.
Since the entire access control system runs on MVC area-controller-action sets. No one initially has no access (including super admins) and we gradually define which parts the groups/users has access to. And we give super admins and admins exclusive access through a claim. Access to everywhere is denied by default.
Once I Auto generated the AREA-CONTROLLER-ACTION sets, I let the user select which group has access to which item.
When the user logs in, I get all the resources the current user has access to and store them as claims. Based on that, using a claims auth manager, when a user request access to some resource, I can check their claims and decide if they should be given access to.
foreach(var claim in permissionClaims) {
var parts = claim.Value.Split(new [] {
'|'
}, StringSplitOptions.None);
if (parts.Length == 3) {
//var httpMethod = parts[0];
var action = parts[1];
var api = parts[2];
//Current.Log.Warn("Checking Access : " + req + " [action: " + action + "]");
// is this request for a API action?
if (api.Contains("API")) {
// if so, req must be for a API action
if (req.Contains("Api") && action.Contains(req)) {
Log.Trace("User has access to API : " + req + " [action: " + action + "]");
return true;
}
} else {
// this is for a MVC action
if (action.Contains(req)) {
Log.Trace("User has access to MVC : " + req + " [action: " + action + "]");
return true;
}
}
}
}
I have explained the approach in detail here - ASP.NET MVC Fine Grained Identity & Access Control.

WebSecurity.CurrentUserName and User.Identity.Name are null after logging in

I'm stating to figure out SimpleMembership for my ASP.Net MVC 4 site. I've augmented UserProfile with a custom property, AccountTypeId. I've updated the database table with the augmented property and can save data to the database when registering. I'm a bit confused about how to retrieve data about the user once they have logged in.
In my Account controller, I have a Login action that gets posted to when a user logs in. Here's my code:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
var userName = WebSecurity.CurrentUserName;
var identityName = User.Identity.Name;
var currentuserid = WebSecurity.GetUserId(model.UserName);
var context = new UsersContext();
var user = context.UserProfiles.SingleOrDefault(u => u.UserId == currentuserid);
var accountTypeId = user.AccountTypeId;
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
WebSecurity.CurrentUserName and User.Identity.Name are both empty strings, however, I can retrieve the UserId using WebSecurity.GetUserId(model.UserName) and can therefore retrieve the user data and I can get accountTypeId.
What's strange is User.Identity.Name gets displayed on my page when it's being called from a .cshtml page after the user is redirected to the landing page. So, somewhere in between the Login action of my controller and the destination page, User.Identity is getting set with data.
I'm assuming since I'm past the WebSecurity.Login check, that WebSecurity would have information about the logged in user, but it doesn't seem to be that way.
What am I missing?
The username is written to a cookie. In order for cookie data to be available, it must be placed into the Response and sent back to the browser. Upon the next request, the cookie value will be read and used to populate the User.Identity.Name property.
In other words, the User.Identity.Name property should be an empty string until after your Redirect call. This is the purpose of redirecting after signing on: to write the cookie to the browser so that subsequent requests will treat the user as signed on.