Ask HN: What are the best practices for web authorization at the moment?

75 points by keth ↗ HN
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.

58 comments

[ 0.23 ms ] story [ 118 ms ] thread
Claims based authentication and authorization is all the rage.
Oh, 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") || ...)?
Attribute/Claims based access control seem to be the most recommended these days. Legacy apps will tend to stick with the role based approach.
Do 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).
Defining 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
And if you want to make that readable, you could use enums or something similar in your language of choice.
I 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.

Cancancan is one best tools to do this in Ruby. Maybe its DSL-like syntax will give you better ideas.

https://github.com/CanCanCommunity/cancancan

Oh, 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.
Yeah cancan was revived by Cancancan :)
In 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.

I 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.

Definitely checkout https://en.wikipedia.org/wiki/Two-factor_authentication .

If a password is compromised, data is protected.

That's authentication, but OP is asking about authorization.
That 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.)

(comment deleted)
I 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

I 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.

Unfortunately (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).
This 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".

  >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.
The 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.

Yes, 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.
I 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.
And 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?

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.

What 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....
Ok. I guess the difference is I would never suggest RBAC to be "relatively simple". There are so many considerations (are negative permissions required? are there many to many required? separation of duties?) that without making things explicit it's hard to know what is meant by "role/permission based authorisation".
I guess it depends on the size of the project/site/page but the system of users roles and rights doesn't have to be complex and can provide a good amount of flexibility. It can be as simple as one more table in the db and a couple of lookups or a join.
Your sense of smell might be on point here. I dont know. But I've noticed that I tend to sometimes over-engineer things. Maybe because I did the exact opposite in the past and had to rewrite large parts of a hobby project because it was to tightly coupled.

Took a while to answer because apparently I submit my comments too fast. :O

I tend to agree with olalonde. What you've described is a well accepted method to handle authorization (sounds like you've implemented role-based access control). But for things like, can't delete yourself, etc., those in my mind are business rules and not authorization rules. In fact that's a validation rule and not authorization rule.
If you're really looking to overengineer this, look at SAML. ;-)
One 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.
You 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.

Where 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

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']
As an unashamed Italophile, I very much liked the "DOC" at the bottom of the README in the repo!
This 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?

Insisting 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.

I 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.

You 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.

Sorry 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

Just poking around, could you explain why https://jsperf.com/indexof-vs-object-lookup/32 indexOf is the fastest in this revision?
That 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.

Thanks for the explanation!
It 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.

I'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

It let's you say, "Moderators can delete posts", "Users can create posts", etc

Can't you do some simple inheritance stuff instead? Like User has_one Admin has_one SuperAdmin. Then join across your models.
The way we allow developers to model this sort of thing out at Stormpath (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

CustomData in Stormpath is so damn useful.

Looks like you have a typo in your email. Guessing should be:

> randall@stormpath.com

You 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!