I am trying to build a permission based Authorization for Asp.net Core while I am moving to core from .net framework. I built an Authorization system based on my needs(using Identity). Basically I am adding Claims to Roles and Users have roles. I built checkboxes for assigning roles to user and assigning claims to roles. Everything works well but here is my question:
While the system is working, let's say user1 has "Admin Role". user1 can access the pages based on the policies which are using his roles->claims. The problem starts here. While user1 is logged in, if I change the claims of "Admin" role, user1 won't be effected until he/she log off and login again.
Anyone has an idea to solve this problem?
The solution seems to work... but let me raise up a caution a bit...
Performance issue
Since you have to check the permission on database for every client request (which will have a real burden to the system). I know it seems like you're building classic mono app. But the server will still suffer from coming back and forth database hard.
The user doesn't know what's happening.
Imagine you display a report section that user usually access it frequently, but on some nice day... the browser blank out, or pop-up some dialog that she doesn't have permission to using this anymore. That's could cause real issue cause user only use what they need at the very moment. What'll happen if it's 10 minutes to the meeting and an assistance needed to print out some report and that pop-up ? (from my experienced lesson (XD)).
So I highly suggest that, on app deployemnt and user login, take all their role and claims from database once and cache them somewhere (like IMemoryCache, since we are targeting classic mono app), then check the claim on caches afterward.
Everytime user permission changed, update the cache, and log the user out right at that moment. If something bad happen. User would yelling at the person who setting the permission, not us as developers.
Seems like you have spend a few continuously hours to complete your own decent solution since last time.
Good work mate
Okay. I came up with a solution.
internal class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
UserManager<User> _userManager;
RoleManager<IdentityRole> _roleManager;
public PermissionAuthorizationHandler(UserManager<User> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
if (context.User.Identity.Name == null)
{
return;
}
// Get all the roles the user belongs to and check if any of the roles has the permission required
// for the authorization to succeed.
var user = await _userManager.GetUserAsync(context.User);
var userRoleNames = await _userManager.GetRolesAsync(user);
var userRoles = _roleManager.Roles.Where(x => userRoleNames.Contains(x.Name));
foreach (var role in userRoles)
{
var roleClaims = await _roleManager.GetClaimsAsync(role);
var permissions = roleClaims.Where(x => x.Type == CustomClaimTypes.Permission &&
x.Value == requirement.Permission)
.Select(x => x.Value);
if (permissions.Any())
{
context.Succeed(requirement);
return;
}
}
}
}
and then we call this in startup.cs
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
For more detailed answer please check: https://www.zehntec.com/blog/permission-based-authorization-in-asp-net-core/
Related
Basically I've spent the last few days trying to figure out how to add simple Admin and Member roles onto a website I'm developing for a friend. (I am using ASP.NET Framework 5.2.7.0). I know that Microsoft has a nice role based access feature built in which allows you to put something like [Authorize Role=("Admin") at the top of the controller; however I have not been able to get it to work at all and most of the resources I've found are for ASP.NET Core.
I've tried modifying my web.config file to enable the role based access (and hopefully migrate the roles and such to my database). But since I've been unable to figure any of this out, I've tried going a more hacky route. (**I am not an advanced programmer, I've been doing this for about a year now and am in no way a pro). This is what I've basically come up with in my attempt to verify if a user is an admin (which also didn't work).
[Authorize]
public class AdminController : Controller
{
private LDSXpressContext db = new LDSXpressContext();
public ActionResult AdminPortal()
{
IsAdmin();
return View();
}
private ActionResult IsAdmin()
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
var currentUserObject = db.accounts.Where(x => x.clientEmail == name);
Account currentUser = new Account();
foreach (var user in currentUserObject)
{
//I loop through the results, even though only one user should
//be stored in the var CurrentUserObject because it's the only
//way I know how to assign it to an object and get its values.
currentUser = user;
}
if (currentUser.role == 2) //the number 2 indicates admin in my db
{
return null;
}
else
{
//Even when this is hit, it just goes back and returns the
//AdminPortal view
return RedirectToAction("Index", "Home");
}
}
}
Now I'm nearly positive that is is NOT a very secure way to check if a signed in user is an admin, but I was hoping that it would at least work. My idea was when someone attempted to access the AdminPortal, the IsAdmin method would run and check if the user is an admin in the database. If they are, then it returns null and the AdminPortal view is displayed, if they are not an Admin, then they are redirected to the Index view on the home page. However, the AdminPortal page is always displayed to any user and this doesn't seem to work either. I've even stepped into the code and watched it run over the return RedirectToAction("Index", "Home"); action, but then it jumps back to the AdminPortal method and just returns the AdminPortal view. So my question is:
1) If anyone happens to have experience with Role Based access in ASP.NET Framework, I would love some tips on how to get it set up
or,
2) If all else fails and I need to use my hacky method, why does it continue to return the AdminView even when the user is not an admin.
**Note: I know I could create a function that returns true or false if the user is an Admin or not, and then have an if/else statement in the AdminPortal controller that will return on view for true and another for false, however I don't want to have to implement that onto every ActionMethod, it'd be nice to keep it down to one line, or just the [Authorize Role="Admin] above the controller if possible.
Thank you guys so much for any help provided, I've been trying to research and fix this for days now and decided to reach out and ask the community!
At a minimum, you'll want to make some adjustments to what you're doing:
[Authorize]
public class AdminController : Controller
{
public ActionResult AdminPortal()
{
if(IsAdmin())
{
return View();
}
return RedirectToAction("Index", "Home");
}
private bool IsAdmin()
{
bool isAdmin = false;
using(LDSXpressContext db = new LDSXpressContext())
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
// #see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault
var currentUser = db.accounts.SingleOrDefault(x => x.clientEmail.Equals(name, StringComparison.OrdinalIgnoreCase));
// If the email doesn't match a user, currentUser will be null
if (currentUser != null)
{
//the number 2 indicates admin in my db
isAdmin = currentUser.role == 2;
}
}
return isAdmin;
}
}
First off, DbContext instances are meant to used, at most, per the lifetime of an HTTP request. Moving it from the class / controller level and placing it within a using block makes sure that it's properly disposed.
Next, your IsAdmin function really just needs to return a true/false value based on your lookup, and then the AdminPortal action can decide what to do with that result.
Since email seems to be a unique field in your table, use the SingleOrDefault or FirstOrDefault LINQ extension to fetch a single matching record. Which one you use is up to you, but if it's truly a unique value, SingleOrDefault makes more sense (it will throw an exception if more than one row matches). Using the StringComparison flag with the String.Equals extension method makes your search case-insensitive. There are a few culture-specific versions of that, but ordinal matching is what I would normally use, here.
Implementing some version of the Identity framework is a bit too long for an answer here, but it's possible to implement a claims-based authentication scheme without too much work. That's something that probably needs a separate answer, though.
For an ASP.NET SPA app, the code template that ships with VS2013 is very different from the code template that ships with VS2013 Updates 2/3/4. For reference, here's the same template from
VS2013: https://www.dropbox.com/s/nc9t691adg2q9ac/OldTemplate.zip?dl=0
VS2013 update 4: https://www.dropbox.com/s/sk8qajyxy4kx4m3/NewTemplate.zip?dl=0
The biggest difference seems to be that logging in with the original template is done without a postback, entirely through JavaScript (which seems to keep with the SPA spirit). But in the later version of the template, a full postback happens during login. There is a plethora of other changes that seem connected to this main change, for example, in the older version there exists this method inside ApplicationOAuthProvider.cs:
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
using (UserManager<IdentityUser> userManager = _userManagerFactory())
{
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
This method is completely omitted from the newer template.
Would appreciate any insight as to why such significant changes were made. I don't see any advantages, and for a SPA-style app I prefer the lack of postback. But perhaps there were important security or design reasons considered which I should also consider.
Thanks...
-Ben
My guess would be that it was an oversight or lack of time.
I'm sorry if it's not the most glamorous of answers filled with code samples... but the SPA template is not the most used scenario when you do File > New Project.
I can see why it wouldn't get as much as as WebApplication for example.
If you are interested in templates, I would suggest taking a look at SideWaffle which contains tons of templates that could help you get bootstrapped.
So I have an asp.net application (using MVC5, ASP 4.5, EF) deployed on Azure. The application allows users to register and login.
However, once logged in, anyone can see everyone else's data.
What is the best practice to isolate the data belonging to different users so that each user can only see the data he/she creates?
Another small question is how does Facebook separates its users' data?
Thanks
For every piece of data a user creates, store their user ID and only allow users to see data that has their user ID. A couple of examples:
SQL:
SELECT UserDataID, Value FROM UserData WHERE UserID = #UserID;
Pass in the user's id to the #UserID parameter.
LINQ:
using (var entityFrameworkContext = new MyDataEntities())
{
var currentUserData = entityFrameworkContext.UserData.Where(userData -> userData.UserID = currentUserID);
}
Where currentUserID could be the user name or ID from forms authentication, for example: HttpContext.Current.User.Identity.Name.
The way in which I accomplished this was by the following.
In your controller you will need to use
public ActionResult Index()
{
var currentUser = manager.FindById(User.Identity.GetUserId());
return View(db.ToDoes.ToList().Where(todo => todo.User.Id == currentUser.Id));
}
You can then also create an admin role which can then view all the details of users and return ToList(). You then might want to put an [Authorize] method on it to only allow Admins access.
[Authorize(Roles="Admin")]
public async Task<ActionResult> All()
{
return View(await db.ToDoes.ToListAsync());
}
I found the following project of great help in understanding. https://github.com/rustd/AspnetIdentitySample
Hope this is of some help
I'm using the Forms Auth and ASP Universal Membership Provider in an MVC 3 Site. We're persisting the cookie for user convenience.
FormsAuthentication.SetAuthCookie(model.UserName, true)
When we disable a user in the Membership provider like this:
memUser.IsApproved = model.IsActive;
provider.UpdateUser(memUser);
if they have the cookie they can still get in to the site. This is similar to what is described in this post:http://stackoverflow.com/questions/5825273/how-do-you-cancel-someones-persistent-cookie-if-their-membership-is-no-longer-v
We use Authorize attributes on our controllers, and I know that that is technically more Authorize than Authentication. But the certainly overloap so I'm trying to figure out what is the best MVC way to do a check that the user is not actually disabled? Custom AuthorizeAttribute that checks the user against the membership database? An obvious setting/method I'm missing with Forms auth to invalidate the ticket?
Update:
Here’s basically what I'm going with – we use a custom permission denied page which we we use to better inform user that they don’t have rights vs. they’re not logged in. And I added the IsApproved check. AuthorizeCore gets called when you put the attribute on a Controller or Action and if it returns false HandleUnauthorizedRequest is called.
public class CustomAuthorization : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || !Membership.GetUser(filterContext.HttpContext.User.Identity.Name).IsApproved)
{
filterContext.Result = new HttpUnauthorizedResult();
// in the case that the user was authenticated, meaning he has a ticket,
// but is no longer approved we need to sign him out
FormsAuthentication.SignOut();
}
else
{
var permDeniedRouteVals = new System.Web.Routing.RouteValueDictionary() { { "controller", "MyErrorController" }, { "action", "MyPermissionDeniedAction" }, { "area", null } };
filterContext.Result = new RedirectToRouteResult(permDeniedRouteVals);
}
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
// since persisting ticket,
// adding this check for the case that the user is not active in the database any more
return base.AuthorizeCore(httpContext) && Membership.GetUser(httpContext.User.Identity.Name).IsApproved;
}
}
Usage:
[CustomAuthorization()]
public class MyController
Well, you're going to have to check the database regardless, the only question is how you want to do that. Yes, you could create a custom authorize attribute, or you could write some code for the OnAuthorize override in ControllerBase, or you could do it in Application_AuthenticateRequest.. lots of ways you could do it, depends on what works best for you.
The best way, of course, would be to not use a persistent ticket if this is an issue for you.
I pretty much always use Roles and a RolesProvider, even if there is just one role named "Login" - in part for this issue. This way, your Authorize attributes might look something like this:
[Authorize(Roles="Login")]
Where Login represents a basic 'Role' that all "active" accounts must have to be able to log in at all; Every protected action is protected by this, at minimum.
That way, simply removing the "Login" role effectively disables the user... because, in my Roles Provider, I am checking the logged-in user's roles against the database or server-local equivalent.
In your case, your "Login" role could simply resolve to a check on the IsApproved field on your user model.
I have a MVC 3 app. There are mainly two zones regarding security. The first one is mostly to prevent public access, but not really sensitive information. Password strength might be weak since there is not really much harm to do either.
Second zone(Area) is restricted. user must apply for access. If user gets access it gets a certain role(s). So each controller method autorizes the user based on that role.
I want these users to have to change password to a strong password on the next logon before they can go further and access the restricted content.
Example:
User A applies for access.
Access is granted. The password policy for
that user is changed as long as it has access. They MUST
change their password on the next logon, and they cannot change back
to a weaker password as long as they have that role.
Is there any secure way to implement this using the ASP.NET?
Update
I've actually used Chris proposed solution and it works, but to handle the verification of the password itself I actually got some inspiration from Micah's proposed solution too. However, it turns out that overriding MembershipProvider.OnValidatingPassword does imply also having to implement 10 + abstract methods that I really do not need to solve this.
A better solution in my eyes was hooking on to the Membership.ValidatingPassword EVENT. I do this inn App_Start, then I implement my own password validation in the event handler and that solved my problem.
Just to share the solution with you i present it here, toghether with Chris solution this solved my problem and hopefully for someone else too:
void App_Start()
{
//To do custom validation on certain passwords set new event handler
Membership.ValidatingPassword += Membership_ValidatingPassword;
}
private void Membership_ValidatingPassword(object sender, ValidatePasswordEventArgs e)
{
//If the user is a new user, we let registration happen without strong password
if (e.IsNewUser) return;
MembershipUser membershipUser = Membership.GetUser(e.UserName);
Guid userId = Guid.Parse(membershipUser.ProviderUserKey.ToString());
//First check if the pwd is strong enough to be flagged, if so we flag it
//using regex to validate the password (20 char, 2 uppercase so on)
if (MyValidationClass.IsStrongPassword(e.Password, 20, 2, 4, 1))
{
//if the user does not already have a flag we set one
MyValidationClass.SetStrongPasswordFlag(userId);
}
else
{
//If the user needs strong pwd, we cancel the operation and throw exception
if (MyValidationClass.NeedsStrongPassword(e.UserName))
{
e.FailureInformation =
new MembershipPasswordException("Password does not satisfy reqirements!");
e.Cancel = true;
}
else
{
MyValidationClass.RemoveStrongPasswordFlag(userId);
}
}
}
You could write your own Authorize Attribute to accommodate both. You simply need to then use it on the relevant sections of your application:
For example:
public class HasChangedPasswordAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
UserRepository repo = new UserRepository();
var user = repo.GetCurrentUser();
bool hasSecurelyChangedPassword = user.HasSecurelyChangedPassword;
return hasSecurelyChangedPassword;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult("/Account/ChangePassword");
}
}
The above will check that the user has securely changed their password. If not it will redirect them to a new page in which to change their password. Once they change it, set the flag as changed.
You can then use it like this:
[HasChangedPassword]
[Authorize(Roles="SuperRole")]
public ActionResult MySecureAction()
{
...
}
You could obviously integrate both of these attributes into one, but for the sake of showing the example they are seperated above.
you will need override the MembershipProvider.OnValidatingPassword
http://msdn.microsoft.com/en-us/library/system.web.security.membershipprovider.onvalidatingpassword.aspx
Probably a simpler method would be check the strength of the password on the client-side when you user is attempting to enter a new password. Check out this list for some examples using JQuery.
In regard the transaction of upgrading and resetting the password, that's something your code can handle, i.e. a flag in the users table that redirects the user to a new registration page. But when they set the password (and presumably it matches the appropriate strength) it can then be submitted...