I have a system where if the user logs in as Joe with the role "Readonly" then he will be granted access only to read things (fairly obviously) however if he logs in as Joe with the role "Administrator" then he will have access to do administrative functions. However I want him to have to relogin if he wishes to change from the Readonly role to the Administrator role so that he could potentially leave his account logged in as Readonly on a display screen or something without fear of someone hijacking his Administrator priviledges.
Now I also need to be able to log in a Web client via an implicit grant or another server via a code grant and have that service be able to use the same roles as well (while still requiring Joe to log in as the particular role if he isn't already authenticated.)
Now I have been trying to do this with IdentityServer3 but I cant seem to get the role information to be part of the authentication for the user, I tried adding an acr_value of role:ReadOnly to the token request (which then turns into an authentication request if the user is not logged in) but if they log in with the acr_value of ReadOnly and then come back to log in with the acr_value of Adminstrator it just lets them on in because they are already authenticated as the user.
Any tips on what I should be using instead of what I am doing or how I might be completely off base in this OAuth2/OpenID Connect world?
I finally figured it out so for others who might want to do the same thing here is what I did.
First you have build a custom UserService that looks in the acr_values for extra information. Then create a claim for that extra information in the AuthenticateResult.
Second you have to override the ClaimProvider to include your custom claim set in step one in the tokens generated.
Next you need a CustomRequestValidator in order to check if a new acr_value is being set compared to the one you have stored in token being currently used. If it has changed and you want to force the user to reauthenticate you can set 'request.PromptMode = "login";'
And that is it, using that set of steps I can now authenticate a user using 3 values (username, password, and role) and if the role requested changes I can require them to reauthenticate.
Works swimingly.
Related
Hi we're having an issue with ASP.NET Identity.
We have an admin page that allows us to affect users to roles. (Admin etc).
The issue is when we affect a role to someone it doesn't apply for him until he logs out/logs back in.
We tried putting
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.Zero;
});
But it causes too many issues with cookies being passed at each request etc.
Is there any way to either :
Force logout another user
Refresh his claims without relogging?
The only solution I have in mind atm is to use something like a list of users to be updated somewhere (redis for exemple) and check this list in a middleware and refreshSigninAsync() if the user is in the list.
You were close, but I'd recommend you change the configuration.
I've added additional explanation on why you want to do this, in case someone wants to understand the thought process.
The problem:
When your user signs into the app they receive a cookie, but when an account is logged out, the user still has the valid cookie, so they aren't really signed out.
How to force sign-out:
Since a cookie gets re-used for as long as it's valid, you need a way to invalidate a cookie. You invalidate a cookie through a security stamp.
Configure Identity so that it periodically re-validates the cookie. It compares the security stamp in the cookie to the database version, and, if it fails, your user needs to reauthenticate.
Add the following configuration to your services:
services.Configure<SecurityStampValidatorOptions(opts => opts.ValidationInterval = System.TimeSpan.FromMinutes(1));
Now, you simply have to use the UserManager to find the user in the userstore, update the roles (or do whatever), then call the UpdateSecurityStampAsync(user) method. For example:
public async Task<IActionResult> OnPostUpdateRolesAsync(string userId)
{
var user = await _userManager.FindByIdAsync(userId);
// do stuff
await _userManager.UpdateSecurityStampAsync(user);
return RedirectToAction(nameof(SomePage));
}
Now that causes the user to be signed out the next time the cookie is revalidated and is forced to re-authenticate.
Notes:
This is certainly a destructive method in terms of user experience, since any HTTP request that the user does will get terminated afterwards.
Also, it's not really an immediate sign-out, only from the perspective of the application is it immediate. The user does not know when he has been signed out, that will only be apparent when he makes an http-request.
I'm working on a web application based on ASP.NET Core MVC 2.1. It provides the ability to execute several Active Directory related operations. One of them is a web based LAPS client. To communicate with AD, I'm using System.DirectoryServices from Microsoft.Windows.Compatibility.
Since LAPS stores its data in a computer objects AD attributes (ms-Mcs-AdmPwd), I need to query this attribute, e.g. like this:
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, targetDomain)) {
ComputerPrincipal computer = ComputerPrincipal.FindByIdentity(principalContext, IdentityType.Name, computerName);
string password = (computer.GetUnderlyingObject() as DirectoryEntry).Properties["ms-Mcs-AdmPwd"].Value.ToString();
}
So my problem is: I need to do this in context of the authenticated user, because the attribute security permissions already control access to the LAPS passwords. I implemented cookie authentication without Identity querying PrincipalContext.ValidateCredentials() against AD in order to authenticate users. What would be the best way to achieve this without asking the user for login data a second time?
There is a PrincipalContext constructor PrincipalContext(ContextType contextType, string name, string userName, string password), but this would require a way to store the password for each session. Storing the password in the session itself would be a bad idea, I guess.
Furthermore, I could just query the data on server side as ApplicationPoolIdentity or as a dedicated service account (which would then need full access to the relevant attributes of all computer objects) and then implement some logic to determine, if the logged in user is permitted to access this specific password. But this would result in unneccessary effort, since all authorization information already exist as AD DACLs.
I hope this makes sense somehow. So if anyone can push me in the right direction, I would be very grateful. Thanks in advance!
After all, I went with a mapping approach in order to determine, if a user is allowed to access data. If the user is member of a certain AD group containing the admins of a certain department and the target computer object is located in the corresponding OU, the password will be queried and subsequently displayed. ApplicationPoolIdentity executes the actual AD query. Works flawlessly so far.
When I try to use Roles.IsUserInRole("roleX") or any related method I get a database connection issue, as can be seen here. I can access roles in the database and read/write any time before this, so I am quite sure I do not have any connection issues. All users role ID's match fine. The error also happens when trying to access a controller method that is authenticated to a certain role? Once any line relating to role checking is hit the app instantly locks up and errors out after about a minute. The only time I ever get this error is when trying to access roles, not sure if somehow identity is not connecting to the DB? I am at a total loss on this issue, the only thing I can think of is that something with Identity is not set up right. Any ideas are greatly appreciated.
Your problem is that Roles.IsUserInRole("roleX") is not part of Identity framework. This is part of MembershipProvider and you don't want to use it. Reason for getting this error - MembershipProvider tries to be helpful and attempts connecting to a database, a database you never told it about.
If you need to check if current user is in a role use User.IsInRole("RoleX");. Where User is part of a Controller or a View. Or you can also do it via HttpContext.Current.User.IsInRole("RoleX"); This checks the auth cookie for information about roles (all the roles for logged in user are persisted in auth cookie).
If you would like to dip into database to check for roles for an arbitrary user (not the currently logged in one) - you need to use ApplicationUserManager.IsInRoleAsync()
I am adding a custom Disabled column to my AspNetUsers table so that an administrator can temporarily disable an account. (It looks like LockoutEndDateUtc doesn't work exactly as I need.)
But what if an administrator disables an account while the user is logged in? Rather than having to check if the current user account is disabled on every request, I am looking for a way to expire that user's session so that the next request will require them to log in.
I believe this is controlled by a cookie. Is this possible?
Actually this can be automatically done. In ASP.NET Identity in the user store there is a property called SecurityStamp. When you change this the user is forced to re-authenticate with the next request. This is because this field is used to generate the authentication token (cookie in your case). The framework has methods that are built into it for changing this either directly UpdateSecurityStampAsync as well as or indirectly. A good example of when it is changed indirectly is when the identity's password is updated through the framework (ie. calling UpdatePassword or RemovePasswordAsync), or when enabling 2 factor authentication for the identity.
The method to change the security stamp is can be found in the UserManager and is called UpdateSecurityStampAsync. From the documentation:
Generates a new security stamp for a user, used for SignOutEverywhere functionality.
I am working on an ASP.NET MVC5 web app that has the following relevant properties:
Uses MVC5.1, Identity 2.0, not restricted to any legacy stuff - can use newest everything.
A user belongs to at least one organization.
Each organization has at least one user that is an organization admin.
The organization admin can allow users specific rights, such as becoming another organization admin.
From what I have understood, a good way to handle this would be to use claims.
In addition to the default claims, a user could then typically have the following claims:
Organization: {some guid}
Organization-Admin: {same guid as above}
Organization-Publisher: {same guid again}
There could potentially be super-admins, or even super-users, with claims giving them access to different roles in multiple organizations.
Now, lets imagine I am user admin logged on to my administration panel, and I want to grant user user the Organization-Publisher claim for an organization that I have the Organization-Admin claim for.
For simplicity, I have a model object that would look something like this:
public class GrantRightModel
{
public string OrganizationId { get; set; }
public string UserId { get; set; }
public string Right { get; set; }
}
and for the purposes of this example, we get the following values from our admin user:
OrganizationId: The GUID of an organization that admin has an Organization-Admin claim for.
UserId: user
Right: Organization-Publisher
There are a number of things that have evaded me about this way of doing things. Lets say we have an OrgAdmin controller with a GrantRight action;
Will the controller action need to check that the currently logged in user (found using for instance ClaimsPrincipal.Current) has the Organizaton-Admin claim for GrantRightModel.OrganizationId "manually", or are there clever auth attributes for this kind of thing?
What is the correct way in code to add the Organization-Publisher: {guid} claim to user? My controller will get a UserManager as necessary by dependency injection.
If user is signed in to the application when this happens, is there a way to have him automatically have the correct claim starting with his next request? I've seen mentioned ways to do this that would cause the user to be logged out, so that he would log in again and start a fresh session with his new claims. I see why that is the way it is, but given that the user could be working on something that takes a lot of time, it might not be popular to just have him logged out when he tries to submit his work.
There is a bit of information on this on the web, but my google-fu has failed me in finding the one that shows me how to beat these three points.
Anyone?
Thanks!
Regardins your questions.
Will the controller action need to check that the currently logged in user (found using for instance ClaimsPrincipal.Current) has the Organizaton-Admin claim for GrantRightModel.OrganizationId "manually", or are there clever auth attributes for this kind of thing?
No. The Authorize attribute takes an optional list of roles that are checked upon authorization. If one of role claims points to that role, used is authorized with no custom code involved.
What is the correct way in code to add the Organization-Publisher: {guid} claim to user? My controller will get a UserManager as necessary by dependency injection.
You create the ClaimsIdentity with proper claims and then ask the authorization manager to sign in used with the identity. The identity is typically then persisted in a cookie together with claims.
If user is signed in to the application when this happens, is there a way to have him automatically have the correct claim starting with his next request? I've seen mentioned ways to do this that would cause the user to be logged out, so that he would log in again and start a fresh session with his new claims. I see why that is the way it is, but given that the user could be working on something that takes a lot of time, it might not be popular to just have him logged out when he tries to submit his work.
I've read it twice and don't quite get it. When you issue a cookie, the cookie is responsible for retaining the authenticated session. Claims are then rebuilt early in each request pipeline based on the cookie. Numerous requests can be then issued by the user and he is still "logged in" as long as the cookie is not invalidated or the auth token stored in the cookie expires.