I am in the process of creating a web application that allows users to add, edit and share content and need some help understanding how to set permissions for the content. Here is the setup:
There are multiple users.
Each user can add content (an object created from a class called Project)
For each project, the user who added it can set the permissions that restrict what other users can do. To keep it simple, let's say there are two permissions: view and edit.
Item 1 is very straight forward and ASP.NET provides user login and identities. 2 is also simple. What about 3? What is the best way to store and enforce these permissions? One way to do this with code is to have a list in the project class that stores user IDs and the associated permissions.
But are there built in features in ASP.NET to facilitate 3? I am familiar with roles, but I don't think that they would work because each project might give users different permissions. I have also been reading about policies, but I don't understand it well enough yet to figure out if I could use it to handle the permissions.
Please let me know if existing ASP.NET features can be used to manage the permissions. If not, is the approach that involves storing the User IDs and permissions the way to go or is there a better (safer) way to do it?
Related
I have a C# project called Authentication that allows users to login and get their login credential checked and if passes, returns the token.
I want to reuse that C# Authentication project for other Applications that I am designing. Is it a good practice to store all the users from different Applications in one table or is there a better way to go about coding for One Authentication project for many Applications?
It depends.
There is a difference between code reuse and infrastructure reuse. The former is OK as you get a clean separation between the applications. Sharing a database is not recommended as it can introduce security vulnerabilities if you do not design it carefully.
I would also separate the notion of users and accounts. An account is used to log into an application and load the correct permissions. i.e. the account controls what an user can do in an application while the "user" object describes the user itself.
If you separate it like that, it's much easier to create a reusable library as everything related to authentication/authorization is in its own part. That's because the authentication design rarely changes. What differs in applications is typically the information that describes the user and the kind of customization that every user want to have.
Short answer:
Separate everything related to authentication and authorization into an "Account" object.
Collect everything describing the user into an "User" object.
Reuse the account part, but build the "user" part in every application.
Never, ever share the database between services. It will create a maintenance nightmare.
Every DB should be designed around (and after) the Bounded Context handled by the relative microservice.
In your case this means one DB to store the authentication data, then another DB per-service, with the relative data.
It is perfectly fine to have a Users table in separate DBs holding some duplicate data. As long as you're diligent enough and have a good strategy for keeping that data in sync.
Most articles/examples I'm finding regarding access rights for asp.net mvc are related to restricting access to specific controllers using roles but I'm trying to figure out how can specific access rights be assigned to a user that belongs to a specific role.
My scenario is rather simple as the only role that requires access rights at the user level is the "Staff Members" role:
Admin: Access to all features.
Staff: Access to specific features.
Member: No access to specific features.
Ideally, I'd like to implement something similar to the below
#if (#User.IsInRole("IsAdmin") || (#User.IsInRole("IsStaff") &&
#User.HasAccess("DownloadData"))
{
<a href='#Url.Action("DownloadData"....
</a>
}
I've got a separate table where I store the staff access rights and since these are assigned at the user level, I'm using the same Id generated in AspNetUsers so a simple EF/SQL Linq join does the trick when I log in to get the relevant access rights for a specific staff member.
Now my questions are:
Can I expand the #User (Principal) to have a scenario similar to the above by introducing a new function such has HasAccess where I could pass the Access Right that needs to be checked for the specific staff member.
If 1. can't be done, would passing the logged-in user access right to the ViewModel used to build my page be a viable option? Is it too risky? Am I wrong to assume that this should be ok since this is server based code and wouldn't be passed along to the client side?
If 1 & 2 are not suitable, what is the best/recommended method to achieve this?
Thanks
The easiest way would be to create an extension method on IPrincipal or IClaimsPrincipal. This way you can unify the access rights definitions in a single place. If you have lots of access rights checks, maybe you need to add the access rights as claims (if you're using claims) or cache them somehow so you don't need a database access everytime (if they are stored on the database).
Also, you're right that it is the same if you pass the access right to the view, because the view is rendered to HTML. But, IMHO, the first option is much better as you don't need to clutter your Controller with Access Rights checks.
I want to go one step further than simple roles based authorisation (Admin, User, Super User etc)
and instead do Activity based authorisation .
My thinking was to assign activities to logged in users which related to whether or not they could perform a action.
For example
CreateUser
ReadUser
UpdateUser
DeleteUser
I would create pages that relate to the above activities
i.e
CreateUser.aspx
on each page i would do a check to see if the authenticated user does in fact have rights to access the activity.
i would do this by making use of Roles.
for example
IsInRole("CreateUser")
Previous to this i could assign the Activities (Roles) to the authenticated user after successful login
My only real concern with this is that by doing this when i authenticate the user and build the authentication cookie it will include alot (potentially) of Roles for each user.
for example i currently have 60 activities in my system (but this could increase as we add more features - each feature is in itselve a new activity)
If the authentication cookie has to carry approx 60+ roles (activities) would that cause any known issues?
Can anyone suggest an alternative approach ?
You may want to look into IdentityModel framework. It has the base class for building a custom Authorize module to verify permissions based on Resource-Action pattern. But this is built for .NET 4.5, not sure what your platform is.
.NET 4.5 also includes SessionAuthenticationModule (SAM) for web authentication. SAM can cache the roles between user calls, so that you don't have to send them back and forth in a cookie. Here is some more information on how it works.
Use Operations and Permissions, as described in Ayende's blog. He has lots of articles on the topic.
http://ayende.com/blog/3109/rhino-security-overview-part-i
http://ayende.com/blog/tags/rhino-security
What you want is a capability list approach.
The solution to this is a mapping of roles to capabilities, similar to this:
>Online Anon User Admin
Article ReadOnly ReadWrite ReadWrite
Article.List ReadOnly ReadOnly ReadOnly
Article.Edit Hidden ReadWrite ReadWrite
Article.Delete Hidden ReadOnly ReadWrite
Article.Title.Edit * * ReadWrite
In practice, these will be your coordinates:
>(system state)
The system may be "Online, Offline, Maintenance" and maybe more.
Use the initial > to find the start of your matrix in the file (you'll have many of these). In C# you'll have an enum.
On the same line are the roles:
Anon User Admin
Then on the left side you'll have the capabilities scoped into namespaces and actions:
<item>
<item>.<action>
<item>.<field>.<action>
<item>.<field>.<value>.<action>
The cells will contain one of these values:
Hidden, ReadOnly, ReadWrite or *
The * will mean "inherit" from the parent item or field.
This way you'll be able to fine-tune the permissions based on items, actions, roles and the current system state.
A simple parser translating the list into an in-memory structure will do. Don't put this on a DB, it will be a pain. Keeping it on the text-file/in-memory level is better. Add a FileSystemWatcher to read that file whenever it is changed for additional functionality and leverage lazy loading. Also, store it in Application state memory, not in a session.
Remember: the default will be Hidden (not even read access, the item/action will be completely inaccessible to that role.
Your real concern will then be which roles you'll really need (in my experience a role pretty much maps to an UML actor, maybe with some slight variances), and what the items/fields/values and actions really are. When you write an item you can really mean a group of items. There is no need to map the capability list directly unto a database/entity or code structure. The capability list is on a higher language level, it is semantic and bound to the domain, not to the code (I want to stress this because it's the real power behind this approach).
Once you have implemented this approach with a simple parser and helper object (Information Expert Principle is well advised, avoid Singletons) you'll be able to reuse it in many applications.
My website has forms authentication, and all is well. Now I want to create a subdirectory and have it also password-protected, but! I need the subdirectory to use a completely different set of logins/passwords than the whole website uses.
Say, for example, I have users for the website stored in the "Users" table in a database. But for the subdirectory, I want the users to be taken from the "SubdirUsers" table. Which probably has a completely different structure.
Consequently, I need the logins to be completely parallel, as in:
Logging into the whole website does not make you logged into the subdirectory as well
Clicking "logout" on the whole website does not nullify your login in the subdirectory
And vice versa
I do not want to create a separate virtual application for the subdirectory, because I want to share all libraries, user controls, as well as application state and cache. In other words, it has to be the same application.
I also do not want to just add a flag to the "Users" table indicating whether this is a whole website user or the subdirectory user. User lists have to come from different sources.
For now, the only option that I see is to roll my own Forms Auth for the subdirectory.
Anybody can propose a better alternative?
I think you can use this code to validate user with any provider
if (Membership.Providers["myprovider"].ValidateUser("USER", "PWD")) {
//your code
}
You can have a separate web.config file in the subfolder that includes only the validiation settings for that subfolder. Note that you must remove all the other settings, as there are some settings that can only be on application level.
<authorization configSource="alterativeSource.xml"/>
You will simply not be able to achieve this with FormsAuthentication only.
This problem scenario is perfect candidate for using HttpModules. HttpModules can intercept the Request and Response pipeline. You will need to write 2 HttpModules.
HttpModule for Authentication
HttpModule for Authorization
You can merge these modules into one later once your solution reaches in stable state and you are fairly able to manage the complexity.
In your solution, you will need to have a database mechanism that stores the subdirectory and user authorization and authentication mapping data. Your HttpModules can read these data take decision about the Requesting user.
You can start from here.
I could see a better answer if your users were in the web.config file, but since you're using users in a database you're pretty much going to have to use something besides forms Authentication. You'll need to set the sub directory to allow any users and then basically rebuild forms Authentication with sessions. You could make a sub class of the page class and use it to hold your all your routines to authenticate and redirect. Then you won't need to add anything to every page in the sub directory.
I know this is the answer you were probably hoping to avoid but forms Authentication is too simple a solution for this kind of complicated situation.
This can help you - numina.codeplex.com
Usually "authorization" is used after login to determine which directories, resources, etc can be used by the given user.
Have you looked at creating a role for the "main" directory and a second role for the "sub" directory and then applying the authorization tag (i.e. in the web.config)? You will need to implement an authorization provider, but that sounds like it might be a better long term solution than having multiple user tables.
Clarification Regardless of authorization (i.e. credential check or password, etc) you would still need to define the user roles somewhere, so for the sake of argument lets assume a "user table". (Nothing stops this from being an XML file in isolated storage if you wish.)
NB It doesn't actually matter that the users come from different sources. This should be reflected in the schema for the user table (i.e. a flag or another column, etc). If you fight this common pattern you will likely end up doing more work in the long run.
When there is one exception, there is likely to be another in the future....
I've not tried this but I think it would work.
You can have two membership providers (i.e., ASPNETDB_1 and ASPNETDB_2.)
These are specified in the membership provider section of the web.config. So you need a separate web.config in each subdirectory, which I know you can do.
In the root of the site I guess you would not have forms authentication. You could have just a start page that asks the user to choose which subdirectory (or you can just use subdomains (firstdir.mysite.com, secondir.mysite.com) or http:/mysite.com/firstdirectory or http:/mysite.com/secondirectory.
I am not sure of the advantage of this method over virtual directories, though, except that the root can contain some ASP programs that don't require authentication.
I understand you are looking for an "out of the box" solution and don't want to "roll your own". However;the standard membership provider does allow you to establish a profile for each user -- then it is really easy to setup one user maintenance form with a listview for each of your separate users maintenance functions and filter by role or by profile value (e.g., organizational ID). Using the membership provider classes, I personally found this very easy to do out of the box (maybe an hour of creating maintenane forms if that using all drag/drop controls, SqlDatasource + listview -- no VB or C coding required). But, separate providers and webconfigs would do the trick as well.
I'm building a SaaS app and have some issues in dealing with authorization and ASP.NET MVC. I have a previous question and this is kind of taking a cue from comments there. I need to provide somewhat granular security (e.g. lots of permissions) for each user. I realize that any discretionary system can be modeled as a roles system by just creating more roles. But that's a lot more roles than I want to deal with. I don't think roles is going to work for me and would like to work more at the permissions level.
I know the standard response to any question dealing with ASP.NET and authorization is create all your application users as Windows users and implement the ASP.NET Membership Provider. One issue, I'm not going to create Windows users. My question is can the standard ASP.NET MVC AuthorizeAttribute and AuthorizeCore be made to fit with a permissions model?
Also, apparently, the impetus here is really that ASP.NET MVC Caching will break a custom security implementation. Obviously, I don't want my pages to run slowly but I'm not sure I want this caching at all. I'm building a business application; is caching everything really appropriate? It seems that caching would just make concurrency problems much more difficult than they already are. For example, if I am caching all of my customer info pages, including the edit pages, then won't I be defeating any concurrency controls I would have in place (say, timestamp checking)?
If I were you I wouldn't create the users as Windows users, but rather store them in a SQL database. So you now have complete control over how you want to associate your security needs with your users.
Once you do that you can then create a custom security filter by creating a class that implements IAuthorizationFilter. That way you have the control you want to do whatever validation you want, roles based, permissions based, day of the week based, whatever.
You then just attribute your service methods with your new custom security filter, and pass along whatever info you need to ensure that the calling user has the appropriate rights/roles to execute the method.
First of all, you do certainly not need to make application users windows users. the default out of the box MVC has them as users stored in a sql database.
Edit Pages typically should not be cached as they will not really be loaded multiple times with the same data within the cache lifespan. my thought process is that MVC is easy enough to add caching that I would build it out first then performance test to see if it is even a necessary step. (Remember that unless you are looking at large large numbers of client connections then it is typically more economical to make a slightly less performant code and beef up the server hardware.
You do not need to create Windows users to use the ASP.NET Membership provider, it uses SQL tables to store the membership objects. Yes, you can use the Authorize attribute with the membership provider, for example, in a page that only "admins" can edit, you'll use the authorize attribute as follow:
[Authorize(Users = "Admin")]
Also, you don't want to be caching pages where users are going to be editing data, use caching (and you can do it a lot) in areas designated for anonymous users - users with no edit rights.
Hope this helps.