Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

There's an interesting section here about one of my favourite challenges in authorization: how to efficiently return a list of things that the current user has permission to access, without running a "can_access()" permission check on every single one of them (which is bad if you have thousands of items and you want to paginate them).

Their solution is to let you configure rules that get turned into SQL fragments that you can run against your own database: https://www.osohq.com/docs/guides/integrate/filter-lists#lis... - example Rails app here: https://github.com/osohq/rails_list_filtering_sample_app

A team I worked with in the past came to the same conclusion - turning authorization rules into WHERE clauses is a very efficient way to solve this problem, if you can figure out a clean way to do it.




Same conclusion we came to, and the basis of our in-house permission gem for RoR. The most efficient declaration of permissions is to express them as a WHERE statement, and then the implementation of can_whatever() is just inclusion in the collection returned by the WHERE.

Permissions have three moving parts, who wants to do it, what do they want to do, and on what object. Any good permission system has to be able to efficiently answer any permutation of those variables. Given this person and this object, what can they do? Given this object and this action, who can do it? Given this person and this action, which objects can they act upon?

We’ve found most permissioning systems end up with a pick-2 approach, and the most common one to be abused is given a person and an action, give me the collection. This leads to implementing permissions twice, once in code, and once as a query.


Hi, wkirby! I'm the post author, I do DevRel at Oso.

> Permissions have three moving parts, who wants to do it, what do they want to do, and on what object. Any good permission system has to be able to efficiently answer any permutation of those variables. Given this person and this object, what can they do? Given this object and this action, who can do it? Given this person and this action, which objects can they act upon?

> We’ve found most permissioning systems end up with a pick-2 approach, and the most common one to be abused is given a person and an action, give me the collection. This leads to implementing permissions twice, once in code, and once as a query.

I love the way you put this! I'm always looking for good ways to talk about authorization without falling back on jargon and I've never come up with a way to talk about the difference between authorizing an action on a single resource and returning a list of authorized resources that I've been happy with. Would you mind if I adapted this in future writing?


By all means! I enjoyed your article here, and I will keep an eye on Oso on the future. Authorization has become a hobby horse of mine, and I always appreciate people who are thinking about the complexity required to meet real-world needs.


You might enjoy this one as well then: https://news.ycombinator.com/item?id=30878926


Love how you explained that. Quoted it on my blog here: https://simonwillison.net/2024/Apr/16/wkirby-on-hacker-news/


Hey cool, I appreciate it!


Yes! My team came to the same conclusion and are in the process of building just such a library for our platform.

- Actor - who is performing an action

- Policy - what types of possible actions are permitted on a resource for a type of actor

- Permission - actions an actor has been granted to perform.

With the intersection of these three objects you can determine if an action can be performed and actors can be granted granular access.


Hey simon! Oso CTO here.

Definitely one of my favourite problems too! Some additional context for those who don't think about this all the time: in many cases, the solution is as simple as "write some SQL where clauses to do the filtering I care about". e.g. I suspect the vast majority of people have logic like `where tenant_id = ?` or similar and they pass in a tenant ID on every query.

Where things get challenging is when you want to decouple the logic (e.g. have an abstraction in your code, or centralize logic in a service). Because then you're in the world of what's the decoupled API that allows me to filter my database.

The easiest way to do that is just generate return a big list of IDs the user can see, and put `id in (... list of ids)` on the query. But that involves (a) syncing the data to the central service and (b) that list can get pretty long.

And so that's why you would even need to think about turning rules into WHERE clauses in the first place :)


> efficiently return a list of things that the current user has permission to access

I've done this with out-of-the-box Keycloak Authorization Services. There is an entire standards based framework for authorization piggy-backed on OAUTH called UMA2. Keycloak provides an implementation of this. It's young and the documentation is a bit thin, and the learning curve is cliff shaped, but it does what is says on the tin.

My case involved authorizing verbs on a set of resources. The backend generates a permission ticket with an arbitrary list of resources and scopes and obtains (from Keycloak) an otherwise ordinary OAUTH access token containing a UMA2 RPT (Requesting Party Token) claim. That is cycled through the "token introspection" endpoint of Keycloak which returns a clean, simple JSON response with the subset of resources and scopes that are authorized. Net result is two requests for any arbitrary subset of resources and scopes. Nothing is stored or managed by the backend system: all the authorization stuff is in Keycloak. You can submit an open ended request that just dumps everything the user is authorized for.

It's simple enough that, foregoing signature checks that are otherwise performed, I prototype and test this stuff using shell scripts, curl and jq. Since it's all piggy-backed on the existing OAUTH system there is no additional infrastructure.


just wanted to make sure I understand correctly, upon authentication you just bake everything a user has access to (all policies) into a claims part of the JWT?

how it would look like for example if a user has access to 10000 objects: are all of them baked into a token as claims?


> A team I worked with in the past came to the same conclusion - turning authorization rules into WHERE clauses is a very efficient way to solve this problem, if you can figure out a clean way to do it.

For rails specifically, https://github.com/polleverywhere/moat was built with this in mind. It's heavily inspired by Pundit, but let's you write policies at the `ActiveRecord::Relation` level. So `policy_filter(Article).find_by!(id: params[:id])` would run something like `select * from articles where id = ? and id in (select id from articles where owner_id = ?);`.


Nice library


There’s a Django app called Bridgekeeper that goes a fair way toward doing this (of course, via the Django ORM). It’s got some pretty major design flaws of its own, and unfortunately hasn’t gotten much love in quite some time. However I still find the concept / intent to be quite telling. It certainly feels like it’s in the right ballpark.



Tailscale's ACLs (attach users to groups, attach objects to groups, cross link the two in policies) does a bit of indirection to reduce that volume.

e.g.: many objects <-> object tag <-> policy <-> user group attachments <-> user


I’m not entirely happy with the solution, but one big advantage of a JS authorization library called CASL is the same ability to turn rules into queries. I also like that I can create field-level permissions and even feed it a target object and get a reduced representation of that object which will include only fields the user is authorized to modify or read.

Unfortunately, there are a few issues not worth getting into here other than to say I feel that the industry has a very frustratingly uneven set of solutions for authorization, no doubt in part due to it being complicated.


(Oso CTO here). Out of curiosity what do you not like about CASL? It always seemed to have a similar goal in mind which I loved, but I suspect it hit similar challenges we had when replying on ORM integrations.


One big annoyance is including attributes beyond the target (which CASL calls the subject). There may be a plethora of environmental factors I want to evaluate in my rule. The two obvious options are:

1) build the rules programmatically, based on what you observe in the request context. This works fine until you want users to be able to create and assign custom policies and load them from a database.

2) put placeholders in the rule’s conditions, and swap them with the current contextual values when rehydrating the rules for a given request. Fine, except this obviates caching except for the raw rule from a database, and rehydrating dozens or hundreds of rules for every request starts to add up in terms of overhead.

I wish rule conditions could reference a “context” parameter (name not important) so I could create a condition like {userId: context.user.id} and at runtime I could pass the current context when I call can. That way I can rehydrate the rule once. I realize this creates all sorts of complications with serializing a rule to be stored in a database or sent over the wire, but that’s where some special placeholders could be understood natively by an Ability and hooked into a passed in context. However, that still creates an issue if I want to create a condition that is purely based on context ( e.g. {context.user.isTrialUser: false} )

The other thing I have been struggling with is that can with a subject name only (rather than an instance) will match a rule with no conditions along with rules that have matching conditions. I understand the author’s rationalization, but can potentially create unexpected results particularly when you offer a system that lets users build their own policies.

The last issue is CASL is nodejs only and I may need to support multiple platforms. I’ve looked at Casbin because of its multiplatform support and customizable model, but I’ve found it extraordinarily hard to use beyond simple RBAC or claims-based authorization, and it still doesn’t offer solutions like conditions-to-query filters or field-level authorizations.


Thanks for the extensive comment. I had similar experiences with CASL. I implemented the placeholders you described with support for arbitrary context. It also supported joins. It used a mongodb-like syntax that could be used with SQL, mongo, dynamodb or generate an in-memory filter function. Im glad to see others in this thread coming to similar conclusions. My design was similar to the OASIS model.


Ah got it, thanks for sharing! That's definitely context I'm missing from having never used it in an actual application.


if all the data is local why even need cloud authz solution?

you can run SQL locally using sqlite or something for authorization decisions and list filtering (give me everything I have access to)


Authorization can be relational, so JOIN to authz tables and and add WHERE conjunctions and it's enough -- if you have a database application anyways.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: