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.
I can see how #[IsGranted(role)] would work without voters but how would #[IsGranted(post, view)] work without voters? Would it dispatch to the post object? If so, then it is pretty much the same as protocols, yeah.
Follow up question: do you think the above would be helpful as a short-guide on Phoenix?
Yes you're probably right about the voter, I've probably mixed it up with the automatic parameter conversion for routing.
I've done too much Phoenix lately I forgot the rest ;)
About the guide I don't know, maybe a blog post yeah, but there is libs that do all that, maybe there's even too many of them. They are fine but lack the cohesiveness you get with something integrated in a framework.
Assuming you have a `current_user` stored in assigns, I implement basic role access as:
If that's too long: 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:
Then in my Post schema: You may encapsulate in a controller helper like this, although you cannot use it in guards: 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.