I currently have a project that I seem to have ran into an issue regarding Roles and thought I would get some opinions on how to best handle the problem.
The system will require editable, flexible roles that control not only the access of specific areas, but also the use of system functions (Adding Users, Editing Users, Viewing Reports etc.)
The system currently allows users to have multiple roles, each of those roles has explicitly defined areas of access/actions, for example:
Role A can access areas 1,2,3 and can Add Users.
Role B can access areas 1,5,7 and can Modify Users.
Role C can access areas 4,6 and only View Users.
so a User could be in Roles A and C, and thus access : 1,2,3,4 and 6, and could Add and View Users.
My first solution was to create a dictionary that would store all of the possible areas of access/access options into a Dictionary like so:
Dictionary<string,bool>
then when it is instantiated it pulls all of the properties from the database and then iterates through the roles to determine if they are accessible.
All of that currently works just fine - however the project is quite Javascript/jQuery intensive so many of these options are called by client-side functions. I am trying to avoid having to wrap all of these client side functions with:
<%if(AccessDictionary[key])
//Enable or Disable Action
<%}%>
So basically, I am wondering about the following things:
After a user logs in, what is the best way to store this Dictionary? Statically? In the Session?
What would be the best method of storage such that the Dictionary will be easily accessed in the View? (As I currently see no way around wrapping my client-side functions)
Any advice or ideas would be greatly appreciated!
I would store this information in the user data part of the authentication cookie. So when a user logs in:
public ActionResult Login(string username, string password)
{
// TODO: validate username/password couple and
// if they are valid get the roles for the user
var roles = "RoleA|RoleC";
var ticket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMilliseconds(FormsAuthentication.Timeout.TotalMilliseconds),
false,
roles
);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
// IIRC this property is only available in .NET 4.0,
// so you might need a constant here to match the domain property
// in the <forms> tag of the web.config
Domain = FormsAuthentication.CookieDomain,
HttpOnly = true,
Secure = FormsAuthentication.RequireSSL,
};
Response.AppendCookie(authCookie);
return RedirectToAction("SomeSecureAction");
}
Then I would write a custom authroize attribute which will take care of reading and parsing the authentication ticket and store a generic user in the HttpContext.User property with its corresponding roles:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.User.Identity.IsAuthenticated)
{
var authCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
var roles = ticket.UserData.Split('|');
var identity = new GenericIdentity(ticket.Name);
httpContext.User = new GenericPrincipal(identity, roles);
}
}
return base.AuthorizeCore(httpContext);
}
}
Next you could decorate your controllers/actions with this attribute to handle authorization:
// Only users that have RoleA or RoleB can access this action
// Note that this works only with OR => that's how the base
// authorize attribute is implemented. If you need to handle AND
// you will need to completely short-circuit the base method call
// in your custom authroize attribute and simply handle this
// case manually
[MyAuthorize(Roles = "RoleA,RoleB")]
public ActionResult Foo()
{
...
}
In order to check whether a user is in a given role simply:
bool isInRole = User.IsInRole("RoleC");
Armed with this information you can now start thinking of how to organize your view models. In those view models I would include boolean properties such as CanEdit, CanViewReport, ... which will be populated by the controller.
Now if you need this mapping in each action and views things might get repetitive and boring. This is where global custom action filters come into play (they don't really exist in ASP.NET MVC 2, only in ASP.NET MVC 3 so you might need a base controller decorated with this action filter which simulates more or less the same functionality). You simply define such global action filter which executes after each action and injects some common view model to the ViewData (holy ...., can't believe I am pronouncing those words) and thus make it available to all views in a transverse of the other actions manner.
And finally in the view you would check those boolean value properties in order to include or not different areas of the site. As far as the javascript code is concerned if it is unobtrusively AJAXifying areas of the site then if those areas are not present in the DOM then this code won't run. And if you needed more fine grained control you could always use HTML5 data-* attributes on your DOM elements to give hints to your external javascript functions on the authorizations of the user.
Related
Just to be clear: I am NOT talking about claims-based identity validation.
I am building an app in which I make fine use of Identity 2.2 to provide validation. It is sufficient for my needs.
My problem is that once a user logs in, only the first page is widely accessible without storing additional information in the user’s “session”. In particular, when the user clicks on a major internal item (for sake of convenience, let’s call this a “customer module”, the Guid for that customer is stored in a claim held by the user. That way, the user can move from page to page and still have that same customer’s data brought up on every page regardless of what chunk of data the page was meant to display. This claim is only refreshed with something different when they return to the main page and click on another customer.
For security’s sake I would like to ensure that if a claim gets accidentally dropped or set to empty, the user gets shunted back to the main page regardless of where they are in the system, and preferably without having to put code in each and every page action of every controller.
Suggestions? Or am I completely wrong by making use of claims? Because it’s still early enough in the project to make a u-turn if the advantages of a different method are compelling enough.
EDIT:
Just to let people know my solution: Because only one group of people will be accessing this application (the users that interact with companies, this app is to record the interactions and “company information”), I decided to use a base controller. The users would be able to log on and view lists of companies without coming across any page that derived from BaseController, but once they chose a Company to work with, they needed to have Claims set to be able to maintain page-by-page contact with this company’s information. This information would be reset only when they chose a different company, but since there was always a chance that a claim could be disabled, I needed something to automagically redirect them back to the list of companies to re-set the claims. A BaseController that was employed by only those pages where information specific to one company would be displayed was the perfect solution.
A base controller is simple. Just create a controller called BaseController and you’re off to the races. Change any controller that needs to work with this base controller such that they are public class YourOtherController : BaseController.
I initially tried to make an Initialize method to handle everything, but ran into a rather big problem: I was unable to successfully both access and write to my Claims. As in, I was able to either read my claims but not make use of my ClaimWriter extension, or I was able to make use of my ClaimWriter extension but be unable to read claims in the first place. Since Initialize is wayyyy too low in the stack to actually do both these things, I abandoned it and went for an OnActionExecuted method, which ended up being successful. My code ended up being this:
public class BaseController : Controller {
private ApplicationDbContext db = new ApplicationDbContext();
protected override void OnActionExecuted(ActionExecutedContext filterContext) {
base.OnActionExecuted(filterContext);
var principal = ClaimsPrincipal.Current.Identities.First();
var company = User.GetClaimValue("CWD-Company");
var prospect = User.GetClaimValue("CWD-Prospect");
if(string.IsNullOrEmpty(company)) {
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("/");
filterContext.HttpContext.Response.End();
}
if(!string.IsNullOrEmpty(company) && string.IsNullOrEmpty(prospect)) {
var id = new Guid(company);
var prospecting = db.Prospecting
.Where(x => x.CompanyId.Equals(id))
.Select(x => x.ProspectingId)
.ToList().SingleOrDefault();
if(prospecting.Equals(Guid.Empty)) { // null prospecting
User.AddUpdateClaim("CWD-Prospecting", "");
} else { // fill prospecting
User.AddUpdateClaim("CWD-Prospecting", Convert.ToString(prospecting));
}
}
}
}
I am probably going to change the if(prospecting.Equals(Guid.Empty) part of the Prospecting section to automagically create the first entry in the db (with all null values except for the ProspectingId and the CompanyId, of course), but this is what works for now.
That's a fine use of claims you describe, no need a u-turn. What you need is a MVC filter, authorisation filter. Something like this:
public class MyAuthorisationFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var principal = HttpContext.Current.User as ClaimsPrincipal;
if(!principal.Claims.Any(c => c.Type == "My Claim Name"))
{
// user has no claim - do redirection
// you need to create 'AuthenticateAgain' route to your table of routes
// or you can do other means of redirection
filterContext.Result = new RedirectToRouteResult("AuthenticateAgain", new RouteValueDictionary());
}
}
}
Then you can add it globally in your filters configuration, but you'll have to exclude your authorisation page from this filter. Or apply on per controller basis - whenever this needs to happen.
This is very basic form of filter - a lot of checks are stripped out, but it gives a general direction how to proceed.
Update
This is a good article about Authorise attribute.
Here use of AllowAnonymous attribute is explained
The way you use it - depends on your scenario. In most cases when you only expose a login page to the world - it is sufficient to add this attribute as a global filter (see second link, part about RegisterGlobalFilters) and then sprinkle [AllowAnonymous] on top of controllers/actions which should be exposed without authentication.
Another approach is to have a base controller that has your attribute applied. And then all your controllers inherit from this base controller. This is more sufficient when global filter does not cut it: cases when you expose different pages to different users - think companies and customers. Your controllers for companies will inherit CompaniesBaseController that has [CompaniesAuthFilter] and customers will be inheriting from CustomersBaseController with [CustomersAuthFilter].
I have a role system where the Admin can create new roles and grant Read, Write, Edit and Delete rights based on the page menu (each page menu has it's own controller) then assign that role to the user.
Users can also have multiple roles but i got that covered. The only problem I am facing is this: since Role assigning can be done dynamically from within the application i can't use the [Authorize(Roles = Role.Admin)] attribute over controller actions.
Instead i was thinking of creating [Read], [Write], [Edit] and [Delete] attributes that i would put over the actions to let me know what that action is for and then allow or deny users to call that action.
Would this be the correct approach? What would i need to have in those custom attributes and how would i allow/deny user to call them? Anyone have any similar experience?
Let's say i have Home and Admin controller and Role1 and Role2 with following rights:
Would something like the above approach works? How do I define custom access for controller actions?
I want to avoid having all the actions in the database and simplify the administration on the site.
In this way administration can be done on R/W/E/D flags on controller level instead of Role1 can all Home/Index, Home/GetRecords etc... action level.
I have same requirement for my project. In my project I have to allow user to access some pages and some pages to admin. Mostly admin can access everything, but I have to block user to access some pages/Action. I did below thing.
a. Create one class to Authenticate user/admin.
b. Create one class for User and inherited that class from "ActionFilterAttribute", and override the OnActionExecuting method.
c. Create one class for Admin and did samething as mentioned in "b", but for admin.
d. put the Authentication class name above the class name or method depending on access.
see sample code(step b). I removed some code, but you can get the idea.
public class AuthenticateUser : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
//Do session check.
if (HttpContext.Current.Session["Id"] == null || String.IsNullOrEmpty(HttpContext.Current.Session["Id"].ToString()))
{
HttpContext.Current.Response.StatusCode = HttpStatusCode.Forbidden.GetHashCode();
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult { Data = new { Status = HttpStatusCode.Forbidden, LogonRequired = true, result = "Session Expired" }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
else
{
//redirect to login page
UrlHelper oUrl = new UrlHelper(HttpContext.Current.Request.RequestContext);
string s = oUrl.Action("", "UserInfo/");
HttpContext.Current.Response.Redirect(s);
}
}
//If usertype is User then allow it. If user type is admin then return redirect.
//Redirect code if admin
UrlHelper oUrl = new UrlHelper(HttpContext.Current.Request.RequestContext);
string s = oUrl.Action("", "UserInfo/");
HttpContext.Current.Response.Redirect(s);
}
}
I would define it differently, just give each user a set of roles.
Define each user with more than one role, if someone has access to write, you set a role for him to write, if he can also read you can add another role - read.
Now when you define your actions you can use this format[Authorize(Roles = Role.Read)]. Set each action by the access right it requires and you are done.
Use claims. each public controller action claim that you need to define.
Then have your roles defined by the list of claims/operations they can do.
Here's a great explanation here Role-based access control (RBAC) vs. Claims-based access control (CBAC) in ASP.NET MVC
So I'm building a web application in .NET using c#, MVC and sqlexpress. I want users to be able to login and depending on what group they belong to, see some part of an UI.
I have created the tables for groups and users in my database, created models from those tables using ADO.NET wizard for model creation.
I have added a controller that has methods for checking if a user exists, and if his password is correct. My question is how to store the information that the user is authenticated the "©correct" way?
At the moment, i just create a new object in the session variable that is made available by System.Web.Mvc.Controller. I have made a flag object Session["Authenticated"] = true and created another object that holds the information (username, group affiliation etc...) as Session["User"].
I have stumbled upon articles that describe implementing your own membership provider (here and here) but I feel that I would need to break my existing classes for password security and account control in order to implement them inside of the custom membership provider.
Is the custom membership provider implementation necessary or is the data saved in the session good enough?
A better way if you don't want to go down the membership provider route would be to look into forms authentication (http://msdn.microsoft.com/en-us/library/xdt4thhy(v=vs.100).aspx).
Firstly save Roles and Permissions regarding user and write custom Action filter in which check If User have permission for access the controller method if yes then go as its going if not then generate exception and response message.
When User Login then also Get Current User Roles and Permissions and save it on Session in custom filters or on your buttons and tabs check from Session User have permission or not if User have permission then show the tab otherwise hide it.
Here is link for Example
Action Filters
Filters Example Code Project
one of the most easy way to implement authentication and authorization in an ASP.NET MVC project is to use the inbuild Forms Authentication module. When you create a new ASP.NET MVC project, it already incorporates all that is necessary to get you started with the Forms Auth.
All it will do is to create certain tables (5), and you never have to worry or track them every in your code. Its all very simple. You can always map the users created by ASP.NET MVC membership provider to your very own UserMaster table.
And next, all you have to do is to mark your ActionMethod or your Controller or in global.asax the [Authorize] attribute, and you are all set.
Code will be as simple as :
for login:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password,
persistCookie: model.RememberMe))
{
///rest of the logic
}
}
also below are some userful functions which will come in handy:
if(WebSecurity.UserExists(username)){ /// ur logic }
//or
WebSecurity.CreateUserAndAccount(username, password);
//or
System.Web.Security.Roles.AddUserToRole(username, UserRole);
and guess what, you don't have to worry abt setting some flag in some HttpModule or first always executing function to see if the session User exists or not, [Authorize] attribute takes care of that.
Mapping with custom UserMaster
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password,
persistCookie: model.RememberMe))
{
try
{
try
{
//below is my custom method IsValidUser with my own logic for
// valid user
MembershipCore.IsValidUser(model.UserName);
if (MembershipCore.isFirstTimeLogin(model.UserName))
{
//Show License Screen in a window
return RedirectToAction("License");
}
//my custom method setting custom Session variables
MembershipCore.SetLoggedInUser(model.UserName);
}
}
}
I have 3 different types of users (with different roles) interacting on my web application, they all perform some task - some can be exactly the same e.g. create a quote others can be unique to that specific user e.g. sign off quote.
For more clarity 3 types of users: Client, Supplier, Customer.
Client or Customer can create a quote, however only the Customer can sign off a quote.
How do I ensure my application allows clients to access client speficic controllers and suppliers to access supplier specific controllers or areas. Via Custom Attributes? Do I store the type of user inside a cookie? Is this safe? or Session state? As soon as someone logs onto the system I send back a LoggedOnDTO object on which I store Username, UserID, and type of user....
NOTE: I went away from asp.net build in way of creating users, I have my own custom tables with my custom mechanism for logging into the system. I have a registered Model Bindiner that looks for the prefix and I send in a strongly typed object to each action...
Sample code:
[HttpGet]
public ActionResult AddComment(int quoteid, ClientUserDTO loggedonclientuser)
{
}
[HttpGet]
public ActionResult AddCommentSupplier(int quoteid, Supplier loggedonsuppluser)
{
}
EDIT: This method for some reason seems so much simpler... Is there something wrong with it? Any possible security issues? Threading?
My session controller is:
if (_authService.isValidUser(model))
{
var data = _authService.GetAuthenticationCookieDetails(model);
AuthenticateCookie.AddDetailsToCookie(data);
return Redirect(Url.Action("Index", "Activity"));
}
When I create my cookie... I can simple store "ClientUser", "Supplier" or whatever role they are inside the cookie.
Then I can create an Attribute and read in the cookie data to see if they are a valid user e.g.
public class ClientAuthorizationAttribute : AuthorizeAttribute
{
public bool AlwaysAllowLocalRequests = false;
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
if (AlwaysAllowLocalRequests && httpContext.Request.IsLocal)
{
bool authorized = false;
var result = UserDetails.GetTypeFromTicket(httpContext.User.Identity as FormsIdentity);
if (result.Equals("client", StringComparison.OrdinalIgnoreCase))
{
authorized = true;
}
//throw no access exception?
return authorized;
}
return base.AuthorizeCore(httpContext);
}
}
Register the attribute under my base controller and I have a simple working solution???
Write a custom MembershipProvider and a Custom RoleProvider then you can decorate your controler class or specific methods with the attribute
<Authorize(Roles:="ROLENAME")>
You can learn how to make that your asp mvc use the custom membershiprovider in this question It's really easy.
Edited:
The way you did it looks right, but I think you take the long way. Implementing your own MembershipProvider and your own Roleprovider will take you no more than 20 minutes... and you will have the benefits of being working with a well tested and documented system and still having the benefits of use your own database tables for the login. In a simple login system, you only have to write two functions in the roleprovider (GetRolesForUser and IsUserInRole) and only one function in the membershipprovider (ValidateUser) and you will get your system working.
If you want, I can put somewhere (maybe pastebin) a well commented versión of a membershipProvider as well of a roleprovider that i'm using in a simple app (they're made in vb.net but i'm sure it will not be a problem)
You can also write generic code in the base controller instead of decorating each action with Authorize attribute. Please refer below url.
Good practice to do common authorization in a custom controller factory?
custom-controller-factory/5361846#5361846
I already know about User and Role-based security in ASP.NET MVC. But now I need something a little more granular.
Let's say I have a list of documents, some of which the user is authorized for, some not. Each document has a corresponding record in a documents table in a database. Documents can be downloaded for viewing, if the user has security access. Documents can also be added, if you have the role. Each document has an URL, and each document list has an URL.
I would like to security trim the list so that the user only sees those documents for which he is authorized. But I also need to authenticate the URL requests for these lists and documents, since there is nothing preventing a user from bookmarking a document they no longer have access to, or simply typing the URL into the browser.
Is the built-in role-based security model suitable for this, or do I need to create separate, table-based security? Can I put the security in my repository, so that the returned records are already trimmed, or should it be part of the controller? Do I need a security attribute to validate the controller request, or should I just put it in the controller method as the first few lines of code?
#Robert, I think you've already answered your own question when you said you should trim them (before) they reach the view. So in your Business logic, as a preference over the repository, you might want to do a lamda to trim off the excess so to speak.
Im my opinion I would never return any records to the view that the user wasn't allowed to see. Why increase risk and traffic?
As for the bookmarks I think there you're going to need to do some business logic preventing them from going to the url when access no longer exists.
I thought the controller was simply there to service the data to the page and not to have any logic as such so I'd prefer the business layer approach for this one as it does appear to be a business rule.
That might not be what you had in mind but unless there is a better approach it's the one I would use.
I'll try to explain how I intended to implement this in my project. The requirement is similar as yours: Users have Roles which have Permissions and everything can change from Permission definition, Role's Permission list, and User's Role list etc. So in one moment it's possible that User has access to something and in another, if Administrator alter something, he does not have access.
Before I put some code, I'll answer to your questions.
Do I need to create separate,
table-based security?
-Yes
Can I put the security in my
repository, so that the returned
records are already trimmed, or should
it be part of the controller?
-I think security should be a part of business logic so I would put it somewhere in between controller and repository.
Do I need a security attribute to
validate the controller request?
-In my project, I've put it in attribute, but sometimes i need to access it from controller to, but since that I keep security logic in business layer, I don't think it is a problem.
First attribute is simple attribute that just allows logged users to execute action:
public class LoggedUserFilterAttribute : ActionFilterAttribute
{
public bool Logged { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!SessionManager.IsUserLogged)
{
filterContext.Result = new RedirectToRouteResult(GetRedirectToNotLoggedRouteValues());
this.Logged = false;
}
else
this.Logged = true;
}
public RouteValueDictionary GetRedirectToNotAuthorizedRouteValues()
{
RouteValueDictionary routeValues = new RouteValueDictionary();
routeValues.Add("action", "NotAuthorized");
routeValues.Add("controller", "Authorization");
return routeValues;
}
public RouteValueDictionary GetRedirectToNotLoggedRouteValues()
{
RouteValueDictionary routeValues = new RouteValueDictionary();
routeValues.Add("action", "NotLogged");
routeValues.Add("controller", "Authorization");
return routeValues;
}
}
and then I have, for example, attribute which allows only SuperUsers to access it:
public class SuperUserFilterAttribute : LoggedUserFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (Logged)
{
MyBaseController controller = filterContext.Controller as MyBaseController;
if (controller == null)
throw new Exception("Please use MyBaseController instead of built in Controller");
User loggedUser = controller.Model.UserBO.GetUserByID(SessionManager.LoggedUser.UserID);
if(!loggedUser.IsSuperUser)
{
filterContext.Result = new RedirectToRouteResult(GetRedirectToNotAuthorizedRouteValues());
}
}
}
}
The MyBaseController is class that inherits Controller and has an instance of Model class which represent container for business objects. In controllers action body, if needed I check users rights on current entity and depending on that I return proper view:
[LoggedUserFilter]
public ActionResult LoadSomeEntity(int customerServiceID,int entityID)
{
UserRights userPermissionsView = Model.SecurityBO.GetUsersRightsOnEntity(SessionManager.LoggedUser.UserID, entityID);
if(userPermissionsView.Write)
return View("EditEntity",Model.EntityBO.GetEntityByID(entityID));
if(userPermissionsView.Read)
return View("ViewEntity",Model.EntityBO.GetEntityByID(entityID));
return View("NotAuthorized");
}
p.s. I'm not sure if I can suggest anything to someone that obviously has much more experience that me :), so if I'm spamming, I apologize for that.