since authorization is important to me as a beginner, I want to ask what are the best practices for web applications at the moment if one needs more fine grained authorization rules than simple roles or permissions for a sideproject (which will probably never turn into something profitable, nor is it planned at the moment)?
Until now I've seen role based authorization which works for simple sites but can easily get out of hand for more complex websites (e.g checks like these: user.hasRole("Superadmin") || user.hasRole("Admin") || user.hasRole("YetAnotherRole") || ... ). If I handle it like in the example, I would need to recompile the application if I give roles access to methods for which they were previously not authorized (if it is not possible to just assign the user to a higher role).
In previous hobby projects I solved this with permission/activity based authorization, where users are assigned roles, and permissions are assigned to roles. The checks within the application are against the permissions not the roles, and can be changed without recompiling the application. If a role needs new permissions just assign it in the backend, if a specific user needs new permissions it's possible to create a new role with appropriate permissions etc.
But how do I handle more complex permissions like this? - Superadmin can delete everyone except himself. - Admins can delete all users but not themself or other admins. - Manager can only delete users that he manages but not himself.
What's wrong with instead looking at some established best practices / suggestions and using those for guidance. What if they said they were looking to do this with encryption? Would you say "Just hash it with ROT-13 and put it in the DB"? Of course not. Others have gone before us...why not learn from where they've done well and not so well?
I have looked at that, and I have learned from both them and experience that authentication and authorization tends to turn into a rabbit hole that takes a lot of effort for very little return. You can do a surprisingly long time with a simple approach, and for most people it isn't worth trying to consider doing anything else.
Took a while to answer because apparently I submit my comments too fast. :O
In my opinion there's only one way to do this. Admins who are all powerful, and non-admins. None of this complexity of super-admins, etc.
Admins have access to everything. Groups have access to things given to them. Users in more than one group have access to the set of all things their groups have access to. Users without a group are really just users in a group of 1. They have access to things given to them.
People who ask for more complicated permissions than this are asking for foot shooting abilities, where people have access to things they don't mean to.
The rest is up to UX. Notify other admins when one behaves badly. Confirm admins really did mean to remove themselves from the admin group.
Let's say I'm on github and I have a number of repositories. I want admin access over my area, the power to create and delete repositories. I do not want other people to be able to create and delete repositories.
And then I lastly want to be able to have access control over some repositories completely (private repositories).
How can you have authorisation that means that I get to sometimes create and delete repositories, that other people can have some permissions for, without having more than 2 levels of authorisation?
Just saying "groups" is just hand-waving the question away, all the complexity of role-based vs attribute based authorisation is hidden in "use groups".
>In my opinion there's only one way to do this. Admins who are all powerful, and non-admins
That's the best way to do it when you can trust your admins, but it violates Sarbanes-Oxley. I've seen some places that literally have a chain-link fence with a pad-lock in their data centers to separate access. Obviously that's suboptimal for efficiency purposes, but anyway.var permissions = { "Superadmin": ["selfdestruct",...], "Admin": ["ban",...], "Plebe": ["post",...] };
function roleCanDo(action, role) { return permissions[role].indexOf(action) > -1; }
Your check would look like: if (roleCanDo("someaction", user.getRole()))
Sorry if JS isn't your thing.
You can put a hierarchy of Roles in a database, but the actual role check can (and I think should) be hard-coded.
In your API you might have a method "public void NewProduct()" That individual method should have the ProductWrite permission (role), and you can give that permission to higher level "containers" or "roles". So Admin has ProductWrite, and ContentManager might also have ProductWrite. In your code it's just one check has("ProductWrite").
Does that make sense?
It's more flexible to have a composition of roles, such as "UserCreator + FinanceReportRunner" or something like that.
Admin is a role, can_delete_everything is a permission.
Users are assigned a role. A role can have one or many permissions.
Program code checks for specific permissions.
Example: username => swalsh, role => hn_subscriptor, permission => can_post, permission => can_upvote.
Sounds like you want a linear hierarchy of roles. Attaching a number to each role and making rules based on that number might be all you need. For example:
Superadmin : 100
Admin : 90
Manager : 80
if (a.number > b.number) a can delete bIt works fine, but you need to document what is going on, or at least put some good comments in. You are definitely going to confuse future developers (including yourself) as to why you sometimes check one set of constraints and sometimes another.
I got rid of this once the system got more heavily used though, as it's just too much overhead to keep track of two systems. So if you really do plan on staying small it's fine, but if you think this might grow, just stick to roles.
Those are the top-level resources we make available for 'modeling' authorization stuff.
Here's how it works (you can replicate this sort of setup in your own systems if you want):
A Directory is a 'bucket' of Accounts, that is unique by email / username. This way, you can segment users into Directories however you wish. If you were running 3 sites with entirely separate user bases, for instance, you might have 3 separate Directories, one for each underlying website. This way each site has its own unique group of users.
An Application is a collection of mapped Resources. So, let's say you have an Application for each website you run. You could choose to:
* Map a single Directory to each Application. This way, when a user authenticates against your Application, their credentials will be checked against a single Directory of Accounts.
* Map multiple Directories to each Application. This way, when a user authenticates against your Application, their credentials will be checked against multiple Directories of Accounts.
* Map other resources Directly (Groups / Organizations).
You've also got the Organization Resource, which is basically a 'Tenant' -- used for designing multi-tenant systems. Each Organization can have multiple Directories / Groups mapped to it directly, and Authentication / Authorization checks can be done against the Organization endpoint directly to control behaviors.
Then, you've got the Group Resource. This basically is a label that you can use to form Many-to-Many relationships between Accounts and Directories.
So, let's say you have a Directory with all your website users inside of it. You could create the Groups:
* Admins
* Super-Admins
* Developers
And assign Accounts to those Groups as needed. When a user is retrieved, you can then pull from their Groups, or vice-versa, to control Group-level information.
Finally -- you've got this big blob of JSON we call 'CustomData'. This is a Resource that is attached to every other Resource in Stormpath.
In CustomData, you can store things like claims / permissions. For instance, if you have an Account object, you might store some permissions like so in your Account's CustomData store:
{ "can_read": true, "can_write": false, "can_delete": false }
By doing a union of CustomData, Groups, Accounts, Directories, and Organizations (each of which have their own CustomData), you can essentially model out very complicated User Authorization patterns across your code base in a pretty dynamic way.
If you'd like to chat about this at all, or would like to just email me some suggestions or whatever, I'm totally open to feedback! randall@stormapth.com
Looks like things are different on vNext though: http://docs.asp.net/en/latest/security/authorization/index.h...
If a password is compromised, data is protected.
It's very common to see a system with a reasonable authentication system which is yet compromised by having little to no authorisation.
(A classic example of this is where after logging in as a normal user, you can still hit the /admin/ endpoint.)
This allows us the flexibility of adding additional roles and permission combinations without making any code changes.
I'm also adding a role editor, where an user with admin role can add or remove particular permissions to each role.
At an application level there is a simple DSL that lets us define abilities on a resource (e.g. a document can be viewed, edited, approved and deleted) and roles (e.g. editor, reviewer, auditor), and to link those two.
On the actor (a user / member of staff) we define a set of designators such as what department they belong to, what position they hold in that department, what projects they are responsible for, etc.
When we then create resources, we link the actor and the designator which is ACL definition. For example, to say everyone that is a manager of department 5 can review a given document we do this:
document.grant :reviewer, :department_manager, 5
Permissions can be added and removed to the resource's ACL on the fly - so for example we could grant temporary access to a member of staff to review a document, then revoke their access once they've given the ok.It's easy to check if a user can perform a given action on a single resource, but we also want to see what actions a user can perform against a collection of resources, for example to present a list of Documents they have access to. The ACL is stored using PostgreSQLs JSONB columns, which can be efficiently queried against:
{ "department_manager:5" => :reviewer, "department:3" => :editor }
SELECT * FROM documents WHERE documents.acl ?| array['department_manager:5']A complex scheme such as AWS IAM policies increases the chance of definition errors.
The business rules don't seem like they would change much over time so coding them in your user management module might be a good compromise.
These are simple, easy to understand, and provide good flexibility.
If you enjoy using roles that's fine too: for example you can create an "Admin" role that grants permissions to do many operations. In other words the Admin role has many claims.
In your authorization code, you check the operation, not the user nor the role.
In your database or ORM, you can create these tables: users, users-roles, roles, roles-operations, operations.
We have a detailed writeup here: https://github.com/SixArm/sixarm_ruby_rbac
A fun benefit to doing it this way is that you can 1) if they have permission, you can make the extra step totally transparent to the user with redirects and 2) if they don't, it also gives you the chance to explain fully why they can't do the thing they wanted to do in a whole nother page.
Try to avoid having a big authorization control center as long as possible. Really when enterprise people request these things, they're trying to account for very specific edge cases in their previous apps that they got burned on. They don't really want to experiment and play with every possible combination of roles and permissions.
If they absolutely insist on having an Excel-like spreadsheet of users and permissions to play with, then just import an Excel spreadsheet and enforce any sort of arbitrary format on them you want.
Just two cents, not bulletproof advice by any means.
You can define access control policies on a resource by resource basis. These can, where necessary, allow or deny permissions to individuals or roles based on the resource and request data.
There's a nice demo / walk through of capabilities here https://github.com/mmerickel/pyramid_auth_demo
The two primary models are Access Control Lists or Role based security. You can google lots of info on these two security models.
In the past for enterprise/b2b I usually build ACL security with a Role based security on top (that is the RBAC underneath is ACLs). Obviously for consumer based products this is massive overkill.
It let's you say, "Moderators can delete posts", "Users can create posts", etc
0 - guest(0) 1 - user(1) 10 - moderator(2) ... 10000000000 - admin (1024) ... 1000000000000000 - superadmin (32768)
basically, a new role had the highest order bit set to 1, then the rest zeros. To add a user to moderator and user (11), or admin & moderator (10000000010), for example, you would do something like this. At the method level I could either do bitwise operations to check for certain bits being set to 1 or I could just check that the decimal was >= a specific decimal value (but this could allow users I may not want having access). The limitations being many, not least of which: what should I set superadmin/superuser to b/c this determines the max amount of future roles that can be added without going through the code and refactoring.
I then moved to a system of roles and user permissions, meaning I had permissions at the group/role level and permissions at the user level. I would merge these permissions together when user is authenticated. At the user level I could remove a role from a user belonging to a group by adding the same permission as found in the group but with an integer -1, or i could add a permission to a specific user with an integer value of 1. I was still checking for role and permission at the user level. This was better in the sense that I could add as many roles as I wanted, as many permissions as I wanted. Merging permissions was a bit of a pain but once it was all worked out it worked pretty good. I was still checking for roles/group membership and permissions at the method level and well this causes a lot refactoring when you want to change what permissions and groups can execute a particular method. I had always felt this was a pain and this system was a bit of overkill but it did exactly what was needed at the time.
I then came across an old article, pretty sure it was this one - https://lostechies.com/derickbailey/2011/05/24/dont-do-role-... - and it led me to the path, right or wrong, that I do now. I create a table for permissions, users, groups. I then create a pivot/join table between groups and permissions, and a pivot/join table between users and permissions. This is so I can give group level and user level permissions. an example permission might look like this: user.create, user.delete, user.update, user.block, user.suspend, etc.. At a user group level a user may have all those permissions but user.suspend, but I may want a specific user to have user.suspend capabilities but not the other capabilities that come at the next level, say moderator, so I keep the user in the user group but give them user.suspend permissions at the user perm level.
Now when those two permission groups are merged (group, users) this makes up the permissions available to a given user.
At the method level I just check for a specific permission ... so at the method to create a user, I check for the permission user.create. At a user update method I may check for user.update permission and maybe the user id (dont want a user updating someone else's profile). The point is that by looking for a specific permission rather than group(s)/role(s) I cut down the amount of refactoring I need to do. Every scenario I've outlined has pros and cons. For instance, what happens if I have a user that can update anyone's profile but i'snt a superadmin or admin?? Maybe create a new permission that give global.user.update and check for both of those. Who knows?!
Hope this gives you some ideas.