How does everyone handle resource level permissions?

Hello!

I’m in the process of building out my backend with Phoenix and Absinthe, but I’m unsure of how to nicely handle resource level permissions, i.e. This user can access document A in Workspace B because they are a member of Workspace B.

My current thought is to have a context method that runs the query to check if the user is part of the workspace that the resource belongs to. It returns back a tuple, either an :error with reason or :ok with the resource. If :ok tuple then either return the resource if thats all you need to do or update/delete/ect.

case can_access_resource(resource_id) do
 {:ok, resource} -> resource
 {:error, _} -> {:error, message: "unathorized"}

This seems to have a clear seperation of authorization logic from what you want to actually do which is good. By returning the resource from the check it also minimizes the amount of calls you need to make to the DB, i.e. if the check just returned a boolean then in the case I was fetching the resource I’d be making another unnecessary DB call.

Did anyone have any better patterns for achieving resource level permissions? Really keen to hear other approaches :slight_smile:

Authorization is very much application dependent. Your approach sounds fine. Definitely better in the context than in a controller or similar.

I’m looking at it in a similar way, but more broadly because I have complex needs - essentially I pass in subject (user), resource (e.g. an order) and requested action (e.g. view, edit, cancel, reopen) into a function that then assesses against multiple business rules to determine whether the requested action is permitted. This means I have to retrieve the resource before assessing, but it does allow me to check view vs edit vs administer rights and other state dependent rules (e.g. can I reopen an order that was closed more than a week ago).

The business rules are encoded as “policy modules” - the main permission-check function iterates each policy module, and returns the first :permit or :deny answer (there can be more sophisticated approaches where policies result in contradictory outcomes).

Authorisation checks can hit the database pretty hard (take a look at your logs), so one thing I have done is introduce a short expiration cache on the results (5 - 15secs).

I did read through a lot of the libraries related to authorisation (e.g. canada, permissionsex) but for my use case they added very little value over and above coding what I needed. I did read quite a bit about policy based access control in general terms - that was very useful.

EDIT: Reading @sb8244 post below, the downside of my approach is that it doesn’t enforce at the database interface level so you have to be pretty careful to ensure users can’t do things they shouldn’t.

2 Likes

I recently implemented access control in a Ruby app. The approach would be similar to how I’d go about it in Elixir. We used an access control list model with a database table per resource. So x_acls y_acls, etc. Each access to the particular object would either verify access with a database lookup, or provide a list query with a join (so data you can’t access isn’t returned). We had various access levels, so used a bit field for that.

The implementation was pretty much what you’d expect based on the above. However, the most important thing for us was knowing that every access to the object was properly verified. We used monkey patching to SQL execution to verify that.

In ecto, you could use the prepare query callback on repo. I would verify that the query has the proper information included in it, or allow a process-wide bypass that serves as a “we verified this as engineers” escape hatch.

4 Likes

Oh wow I would have never thought of module patching repo. I think it’s a bit extreme for everyday use cases, but I can see it being a powerful tool for when you need it. Thanks!

1 Like