5 ms·
Exactly. Since mix phx.gen.auth handles the authN side, it’s really just authZ that will be idiosyncratic to your application logic. In every authorization sy
by AlchemistCamp 3y ago
Exactly.
Since mix phx.gen.auth handles the authN side, it’s really just authZ that will be idiosyncratic to your application logic.
In every authorization system I’ve written in a Phoenix app (which 8 now), I’ve taken this approach above.
Make a pipeline of rules that take a conn or socket and authorize or not based on what’s in the assigns. Better yet, at the very top of the pipleline, wrap the conn or socket with an auth struct that holds metadata like authorization status and any redirection to be done. Unwrap that struct into the conn or the socket at the end of the pipeline, where the status is resolved.
This way, every request can be default unauthorized and you can apply complex, composable rules for granting permission to take various actions on resources.
- lc-0815 3y agocan you provide sample code of one of your auth you have created, I would like to learn how to code the pipelines as your mention thanks a bunch
- conradfr 3y agoIn other words phx.gen.auth generates a lot of code that may or may not in part suits your authentification needs and Phoenix gives you nothing for Authorization ;) (I am a bit bitter after implementing authentication with Firebase and authorization in a Phoenix app? Maybe ahah).
- AlchemistCamp 3y agoThis a bit too negative of an interpretation. In 100% of the apps where I've used mix phx.gen.auth, the code it's generated has been suitable. In some cases, I've used it in conjunction with a library like Ueberauth for social logins, but it's been strictly superior to older workflows using 3rd party services or frameworks that take over the whole user table. Reaching for something like Firebase or especially Auth0 has added effort in the long run in each project where I've inherited that decision. The typical end-state seems to be a soup of logic split between the 3rd party provider and inside the application. It's more difficult to reason about and more expensive to audit. Nothing is going to do your authZ for you, unless it was made with your business logic in mind. Different apps are going to have radically different needs and there isn't a single best solution for all of them.
- conradfr 3y ago> This a bit too negative of an interpretation. Yes :) > 3rd party services or frameworks that take over the whole user table. I've never used a Framework when the auth library can't just use your user schema/entity. I will never agree that using a generator and committing files I didn't write is better DX than using a library and its documentation (which will be easily updatable). It's also kind of limited in scope. Authentication is more than that. Like, JWTs for example. To each their own. > Nothing is going to do your authZ for you The business logic no, the rest yes. How do I limit a controller or an action for admin users in Symfony ? #[IsGranted('ROLE_ADMIN')], or for a specific user? #[IsGranted('edit', 'post')], done. And the auth/auth are available anywhere in the framework. How do I do that in Phoenix ? Pull Bodyguard, write a bunch of "with", plugs, or scope with additional code I have to write and maintain.
- josevalim 3y ago> How do I do that in Phoenix ? 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.
- bongobingo1 3y agoDo you mind briefly expanding on your pipeline process?