I'm building an app in ASP.NET Core 3.1 with Entity Framework Core 3.1 and am having some trouble debugging Authorization. Currently, I'm working on Claims-Based Authorization, as the code samples below will illustrate.
In the AdminController:
public async Task<IActionResult> EditUser(string id)
{
var user = await _userManager.FindByIdAsync(id);
if (user == null)
return RedirectToAction("UserManagement", _userManager.Users);
var claims = await _userManager.GetClaimsAsync(user);
var vm = new EditUserViewModel()
{
Id = user.Id,
UserClaims = claims.Select(c => c.Value).ToList(),
Email = user.Email,
UserName = user.UserName,
Birthdate = user.Birthdate,
City = user.City,
Country = user.Country
};
return View(vm);
}
This function is called when I click on the Edit button next to a user in the Admin section of the app. The problem I'm having lies with the claims. I have the ability to add claims to a user, but somewhere in the code it isn't working correctly. When debugging, everything seems to work, and the database table AspNetUserClaims does contain the correct data after adding the claim to the user, but the function above has a bug in it that I'll try to describe.
Basically, on the EditUser View page, there should be an HTML ul of all claims the current user has. Even after adding claims to the user, though, no ul of claims displays. When debugging, I found that the problem seems to be in the EditUser code above, because the var claims is set to Count=0 after calling GetClaimsAsync(user). This value of Count=0 is then passed to the EditUserViewModel and set to the List property UserClaims. But Count=0 isn't any legitimate value, and I'm not sure where GetClaimsAsync(user) is getting that Count=0 from. As I said, the database table seems to be storing the data correctly.
If you need any more code or further details, be sure to let me know so I can make things easier to understand.
Related
I am using ASP.NET Core 3.1 with Identity and storing some basic user information like their full name in a claim using the code below (I am aware of checking password and stuff, ignoring it for brevity):
var user = await _userManager.FindByNameAsync(Input.Username);
var claims = new List<Claim>
{
new Claim("UserFullname", user.Fullname, ClaimValueTypes.String)
}
await _signInManager.SignInWithClaimsAsync(user, Input.RememberMe, claims);
I am accessing it in the _Layout.cshtml using the line below:
var userFullname = User.Claims.Single(c => c.Type == "UserFullname").Value;
The problem is, this seems to expire in some time even though the user is still logged in. I want this to be perpetual until the user logs out.
I am sure there has to be some way in startup.cs to control this and as far as possible, I would like to avoid overriding anything.
--EDIT--
As mentioned in the comments for answer by #yinqiu, I tried the cookie authentication scheme using the line below:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
But it did not help either.
I think you can try to override the SignInWithClaimsAsync method.
public override async Task SignInWithClaimsAsync(ApplicationUser user, AuthenticationProperties authenticationProperties, System.Collections.Generic.IEnumerable<System.Security.Claims.Claim> additionalClaims)
{
if (authenticationProperties != null && authenticationProperties.IsPersistent)
{
authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddYears(1);
}
await base.SignInWithClaimsAsync(user, authenticationProperties, additionalClaims);
}
This is the appropriate solution of your case:
If you are inheriting Identity Classes (IdentityRole,IdentityUser) into your custom classes then you need to use your inherited classes otherwise you use the default Identity Classes. You need a custom ClaimIdentity Class let assume 'ApplicationClaimsIdentityFactory' and this class should be inherited by UserClaimsPrincipalFactory<AspNetUser, AspNetRole>
Step1 Register your dependencies in Startup.cs
services.AddIdentity<AspNetUser, AspNetRole>().AddEntityFrameworkStores<ICRCOMDMSEntities>().AddDefaultTokenProviders();
services.AddScoped<IUserClaimsPrincipalFactory<AspNetUser>, ApplicationClaimsIdentityFactory>();
Step2:
Override the method CreateAsync in your custom claimsIdentityFactory Calss and here you need to create your custom claims and return like
public async override Task<ClaimsPrincipal> CreateAsync(AspNetUser user)
{
var principal = await base.CreateAsync(user);
((ClaimsIdentity)principal.Identity).AddClaims(new[] {
new Claim("UserLastLogin", user.LastLoginDate.ToString("dd/MM/yyyy hh:mm:ss tt"))
});
return principal;
}
Now your claims persists until user is logged in.
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
When a user logins using the Auth0 lock on my client side, I get an idToken, but also an idTokenPayload which looks like this:
idTokenPayload = {
audience: "AUTH0CLIENTID",
exp: 1494190538,
iat: 1494154538,
iss: "AUTH0DOMAIN"
sub: "USERNAME"
};
Would it be possible to return the userId in Auth0's database instead of the username in the sub field?
The reason I want to do this is that I want to keep Auth0's db for users, and I have on my server-side some Profile, Post, Comment etc entities which have a userId column. Right now before each request on my entities I need to populate the user by doing an extra request: let id = Profile.find("... where username === auth0.sub").getId(); (pseudo-code of course).
With the C# lock sdk, you get back an Auth0User after the call to the LoginAsync method in the Auth0 client. Let's call this variable auth0User. If I look at auth0User.Profile, a JObject (it's a JSON object if you're not using C#), it contains a JSON array named "identities". My identities variable initialization looks like:
var identities = (JArray)auth0User.Profile["identities"];
This array contains all the identity providers associated with the user. If like me you haven't attached any other sign in besides Auth0, there will be just 1 entry here. Each object in this JSON array will contain a "provider" string and a "user_id" string. If the provider says "auth0" then it's from Auth0. Since I don't use FB or other account types I'm not exactly sure what they say. Here's my C# code to get the UserID:
var identities = (JArray)auth0User.Profile["identities"];
if (identities != null)
{
foreach (var identity in identities)
{
var provider = (string)identity["provider"];
if (string.Equals(provider, "auth0"))
{
UserID = (string)identity["user_id"];
break;
}
}
}
I believe that this should all be provided standard without needing to add any rules or webhooks. This article should explain in more detail and also gives examples in javascript: auth0 normalized user profile
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);.
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.