Assuming you have a `current_user` stored in assigns, I implement basic role access as:
def edit(conn, param) when conn.assigns.current_user.role == :admin do
...
end
If that's too long: defguard is_granted(conn, role) when conn.assigns.current_user.role == role
def edit(conn, param) when is_granted(conn, :admin) do
...
end
It also works on LiveViews with guards on handle_event.---
The other way of doing authorization, which IIRC is similar to voters in Symfony, I typically implement with protocols:
defprotocol Authz do
def can?(resource, action, user)
end
Then in my Post schema: defmodule MyApp.Blog.Post do
schema "posts" do
# ...
end
defimpl Authz do
def can?(post, :view, user) do
post.public or can?(post, :edit, user)
end
def can?(post, :edit, user) do
post.owner_id == user.id
end
end
end
You may encapsulate in a controller helper like this, although you cannot use it in guards: def can?(conn, resource, action) do
Authz.can?(resource, action, conn.assigns.current_user)
end
Now you can call it to check against any resource whatsoever. If you don't implement it for a resource or for an action, it will crash as expected.I find it requires less boilerplate than the Voter approach in Symfony (but it has been quite some time since I last checked it). No additional abstractions either. The only downside is that it doesn't work annotation/@decorator style (but if you really want it, it should be doable).
---
However, my favorite way of doing authz is by scoping the queries. Typically all of my context functions receive either the org, the user, or a "session" data structure with both which I use as the starting point of my queries. Then I complement with Authz.can? style when that's not enough.