11 ms·
Ask HN: What are the best practices for web authorization at the moment?
Hi,
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.
- jlhim 11y agoClaims based authentication and authorization is all the rage.
- keth 11y agoOh, the system that will be used in .net mvc? So far I've only seen that permissions and roles can be modeled with claims, but could that not lead to the same problem like with roles (e.g user.hasClaim("foo") || user.hasClaim("bar") || ...)?
- talles 11y agoFor ASP.NET MVC I use to roll my own AuthorizeAttribute (just inherit it and overload OnAuthorization). I avoid dealing with IIdentity and IPrincipal, I find it sorta messy. Looks like things are different on vNext though: http://docs.asp.net/en/latest/security/authorization/index.html http://docs.asp.net/en/latest/security/authorization/index.h...
- opmac 11y agoAttribute/Claims based access control seem to be the most recommended these days. Legacy apps will tend to stick with the role based approach.
- keth 11y agoDo you know of more involved examples that use claim based authorization? So far I have only seen simple use cases that at most involve if a user works for a certain company, or that a user can only delete his own postings (userID == authorID), which I can easily handle with permissions. But I have not seen slightly more complex ones yet like the example in the OP or really complex ones. But will definetly look into it, the .net implementation seems fairly easy to understand (after skimming through some files).
- bearfrieze 11y agoDefining authorisation rules as part of your application sounds fine to me. Aim for making compilation and deployment of your application trivial. 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 b
- praseodym 11y agoAnd if you want to make that readable, you could use enums or something similar in your language of choice.
- Practicality 11y agoI had a hybrid system at one point that included this "access level" in addition to the roles system. So a person could have multiple roles, but only one access level. The access level was only for admin things like this, so most people were just "users." It 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.
- ShirsenduK 11y agoCancancan is one best tools to do this in Ruby. Maybe its DSL-like syntax will give you better ideas. https://github.com/CanCanCommunity/cancancan https://github.com/CanCanCommunity/cancancan
- keth 11y agoOh, cancan is alive? Cool. I already looked at the old CanCan repo and it's the best looking library for inspiration I've found so far. Maybe I should dig deeper to see how more complex authorization rules are handled.
- ShirsenduK 11y agoYeah cancan was revived by Cancancan :)
- Osiris 11y agoIn my application I do this using tables in the database. The database has a roles table, a permissions table, a roles_permissions table and a roles_users table. When a user it looked up, a JOIN is done to pull in all the permissions assigned to all the roles, then there's a configuration that specifies with permission is required to access each API call. This allows us the flexibility of adding additional roles and permission combinations without making any code changes.
- Shorel 11y agoI do exactly the same. I'm also adding a role editor, where an user with admin role can add or remove particular permissions to each role.
- hellbanner 11y agoDefinitely checkout https://en.wikipedia.org/wiki/Two-factor_authentication https://en.wikipedia.org/wiki/Two-factor_authentication . If a password is compromised, data is protected.
- icebraining 11y agoThat's authentication, but OP is asking about authorization.
- eterm 11y agoThat is authentication, which is orthogonal to authorisation. 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.)
- deleted 11y ago[deleted]
- jph 11y agoI do authentication in terms of operations a.k.a. claims a.k.a. attributes, such as "Can Create User". 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 https://github.com/SixArm/sixarm_ruby_rbac
- ejcx 11y agoI think most people answered your question without reading it. They must be mind-readers. 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.
- ironchef 11y agoUnfortunately (and as you know) the world is more complex than this and, in some cases, you will not meet legal requirements. Yes, what you suggested is _a_ best practice; however, I'd suggest some of the more "standard" directions would be (for example) a standard RBAC design (similar to those from NIST or Ferraiolo / Kuhn 1992 or INCITS CS1.1 standards update 2012).
- eterm 11y agoThis really doesn't satisfy real world usage. 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".
- ktRolster 11y ago>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.
- tony-allan 11y agoThe last part of your question looks more like business rules for the management of users. 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.
- keth 11y agoYes, I'm not always sure about what is what. The line between business rules and authorization rules seems to be thin/blurred at times to me.
- olalonde 11y agoI smell over-engineering. It sounds like you would like to keep your authorisation logic nicely decoupled from your code, stored away in a database. As you are starting to notice, you can only get so far with that approach and as you try to make your configuration more powerful to handle more and more edge cases, you will inevitably end up with some sort of Turing complete configuration system. My advice is to stick with something simple and just hard code the edge cases in your code. It may feel dirty but at least you won't end up with a monster like AWS IAM in your side project.
- ironchef 11y agoAnd you basically laid out a recipe for incurring technical debt. 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?
- btilly 11y agoOthers 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.
- olalonde 11y agoWhat I am recommending is an established best practice: YAGNI or perhaps KISS. When I meant something simple, I didn't mean to throw away the whole authorisation abstraction. I do consider things like role/permission based authorisation relatively simple as long as the whole logic doesn't live in a database a la http://blog.bronto.com/wp-content/uploads/2014/10/imagecache/blog-full-colum/magento-21.png http://blog.bronto.com/wp-content/uploads/2014/10/imagecache....
- JoachimSchipper 11y agoIf you're really looking to overengineer this, look at SAML. ;-)
- arihant 11y agoOne way you can do that is by architecting something like chmod. Basically a point based system. 7 gives all, 1 gives read. You can then have your actual implementation compiled away, but can change points for a user as and when you like. The code that checks won't need to be recompiled.
- agentgt 11y agoYou can model most security requirements with either decision matrixes and/or decision trees. These matrices and/or trees can be retrieved or calculated dynamically avoiding you from having code like: user.hasRole("Superadmin") || snip... 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.
- lucaspiller 11y agoWhere I'm currently working we have quite complicated roles which are based upon the position a member of staff holds. Positions may change, and the permissions follows the position not an individual user. We have many applications, and each is free to implement their own roles but we have an open source gem which forms the basis of it: https://github.com/ifad/eaco https://github.com/ifad/eaco 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']
- bshimmin 11y agoAs an unashamed Italophile, I very much liked the "DOC" at the bottom of the README in the repo!
- swalsh 11y agoThis is not a programming problem, this is a Roles design problem. Admin is not a role, its a type of user. You might call it a container if you want... but roles are more fine grained, and should be a hierarchy. 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?
- evtothedev 11y agoInsisting on it being a hierarchy is an easy way to get into trouble. Eventually, you'll want to give someone "just one more permission" It's more flexible to have a composition of roles, such as "UserCreator + FinanceReportRunner" or something like that.
- Shorel 11y agoI think you are confusing roles with permissions. 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.
- chejazi 11y agoYou can stuff the logic of "who can do what" somewhere else. eg: 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.
- striking 11y agoSorry to nitpick, but indexOf is a bad idea. Using it is up to 1700x slower (worst case) than using object indices[1]. It's probably an O(n) operation, where indexing an Object is nearly always O(1). 1: https://jsperf.com/indexof-vs-object-lookup/4 https://jsperf.com/indexof-vs-object-lookup/4
- ar0b 11y agoJust poking around, could you explain why https://jsperf.com/indexof-vs-object-lookup/32 https://jsperf.com/indexof-vs-object-lookup/32 indexOf is the fastest in this revision?
- striking 11y agoThat revision tests the best case (looking for string "a") while the revision I picked tests the worst case (looking for string "xxx", which doesn't exist). I picked it just because it came up first in Google search. Testing in my browser (Safari 9.0.1, Mac OS X 10.10.5) still gives a 54x improvement to Object indexing, though. Perhaps getting the first value in an array is faster than indexing an Object in your browser. The point is, if the thing doesn't exist, this part of the program will be thousands of times slower, and will get slower as the list of roles grows. Meanwhile, the hash map will not.
- ar0b 11y agoThanks for the explanation!
- 11y ago
- sre_ops 11y agoIt is always amusing to see people trying to re-invent a wheel making it square. What you are looking for is the MAC system under the merge down rules.
- zekenie 11y agoI'm working on an open source MEAN stack permissions framework that does some of what you're talking about. Its not fully baked yet, but feel free to give it a look: https://github.com/zekenie/dmv https://github.com/zekenie/dmv It let's you say, "Moderators can delete posts", "Users can create posts", etc
- HugoDF 11y agoCan't you do some simple inheritance stuff instead? Like User has_one Admin has_one SuperAdmin. Then join across your models.
- ynniv 11y agoUsers have roles, roles provide permissions, some permissions are parameterized: http://shiro.apache.org/permissions.html http://shiro.apache.org/permissions.html
- rdegges 11y agoThe way we allow developers to model this sort of thing out at Stormpath (https://stormpath.com/ https://stormpath.com/) is via Applications, Organizations, Directories, Groups, Accounts, and CustomData. 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
- thruflo22 11y agoPyramid has a very flexible security system http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/security.html http://docs.pylonsproject.org/projects/pyramid/en/latest/nar... 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 https://github.com/mmerickel/pyramid_auth_demo
- LukeB42 11y agoAs one of the contributors to Microauth[1] I have to say file-based auth using a dedicated auth server behind the scenes. 1. https://github.com/LukeB42/Microauth https://github.com/LukeB42/Microauth
- Dowwie 11y agoYou implemented what is known as a flat Role Based Access Control (RBAC) model. It is the simplest form of RBAC, which gets much more complex and powerful from there!
- tboyd47 11y agoOne thing you could do is to keep things as simple as possible inside the application but code "confirmation pages" into every restricted flow. So the confirmation page (or controller/Form object/Service/what have you) is what contains the authorization logic, not the main action flow. So every time you have a particularly sensitive action, you just give that action another step, in which you store your messy authorization code. 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.
- levelist_com 11y agoOver the years I have tried many implementations, first using a binary system 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-based-authorization-checks-do-activity-based-checks/ 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.