Pretty much every web application handles authorization, and there are so many different libraries. How do you pick the right one? You're about to find out.
Fundamentally, authorization is a matching game to determine if some actor is allowed access to some resource. There's many ways to skin the cat, but they all follow the rules. Here are my rules:
Authentication presumes authorization. Null Object pattern is your friend here. You can absolutely make a
Guest
orAnonymous
user with no access to anything. There isn't any reason to treat unauthorized users differently besides native language support fornil
.Authorization systems are written from either perspective: the user, or the resource.
Any arbitrarily complex authorization code can be constructed by the basis of the following 5 concepts:
- Users (You, customer, external system)
- Groups (This is a collection of users)
- Permissions (CRUD+, standard CRUD plus whatever)
- Roles (This is a collection of Permissions)
- Resource (Anything! A record, a method, object etc)
In this mental framework Groups are just a collection of users and can be recursively defined. A group can be a tree of groups. Same with Roles.
A Role being a collection of permissions was a bit jarring at first, but think about it for a bit, and you'll realize it's true. There's quite a few of libraries that use role when instead of permission so be careful.
Now you have a solid mental framework to quickly review authorization options based on the trade offs that are being made by any particular library. I have yet to see any authorization library that supports all of them at the same time.
Let's review a few popular ruby gems and we'll see the trade-offs using this framework
- CanCanCan
- Pundit
- Apartment
- acts_as_tenant
CanCanCan
Let's look at cancan
/cancancan
, a popular gem originally created by the infamous Ryan Bates.
Straight from the README:
class Ability
include CanCan::Ability
def initialize(user)
can :read, Post, public: true
return unless user.present? # additional permissions for logged in users (they can read their own posts)
can :read, Post, user: user
return unless user.admin? # additional permissions for administrators
can :read, Post
end
end
You can immediately see the tradeoffs in the short example.
users | groups | permissions | roles | resource | auditable | complexity |
---|---|---|---|---|---|---|
✅ | ❌ | ✅ | ❌ | ✅ | ❌ | low |
CanCanCan supports users, permissions, and resources, limited support for auditing support, shallow learning curve, fail closed access via load_and_authorize_resource
CanCanCan works from the "perspective" of the user.
Supporting groups of users, or roles is left to the developer to implement.
Pundit
users | groups | permissions | roles | resource | auditable | complexity |
---|---|---|---|---|---|---|
✅ | ❌ | ✅ | ❌ | ✅ | ✅ | medium |
Pundit works from the perspective of the resource via policies.
Via policies, Pundit supports resources, users, auditing, a bit complex, permissions, fail closed via after_action :verify_authorized
Supporting groups of users, or roles is left to the developer to implement.
Apartment
users | groups | permissions | roles | resource | auditable | complexity |
---|---|---|---|---|---|---|
✅ | ✅ | ❌ | ❌ | ❌ | ✅ | high |
Advertised as "Multitenancy for Rails and ActiveRecord" means it supports groups. It does so via database replication/sharding/schema_search_path.
This design means that it supports users, groups, and resources, and is trivially auditable. fail open/close depends on how you handle your database connections/search path, and has a steep learning curve and maintenance burden.
It does not support permissions, roles.
acts_as_tenant
users | groups | permissions | roles | resource | auditable | complexity |
---|---|---|---|---|---|---|
✅ | ✅ | ❌ | ❌ | ✅ | ✅ | low |
This gem uses the ActiveRecord's default_scope
to filter out records based on the group. It is quite an elegant solution, even though it doesn't solve permissions, nor roles.
There are various other role based authorization gems that have similar trade-offs. (Spoiler: They never support groups)
If only there was a gem that supported it all:
- Trivial to use
- Groups
- Users
- Roles
- Permissions
- Trivial to Audit
- Fail-closed design
- Can work from the perspective of a user or resource
That gem doesn't exist.. yet
Top comments (0)