loading...

How to implement Authorization in elixir and phoenix

haseebeqx profile image Haseeb Updated on ・2 min read

We are going to build a very simple user authorization system for phoenix framework which can fit into applications of different size.

Basically authorization is allowing a user or an entity to do something if it is allowed to do that. There are multiple approaches to address this. But this one matches my requirements and it allows me to easily apply this logic between multiple projects. As usual there are libraries to do this. But in my case it was not necessary.

So lets assume we want to authorize "a user who created a post is the only one who can delete the post". Basically, We can simply create a function to do that and call it from the action accessing it.

def authorize_delete_post(user, post) do
  if(user.id == post.user_id) do
    {:ok}
  else
   {:unauthorized}
  end
end

This is the simplest type of authorization. It will work fine, But if you are reading this it is because you need a better solution.

Authorize module

It is recommended to move all authorization logic to a common place. So, we can create a new module Authorize and move it there.

defmodule MyApp.Authorize do

alias MyApp.Accounts.User
alias MyApp.Posts.Post

 def check(:delete_post, %User{id: user_id}, %Post{user_id: user_id}), do: {:ok}
 def check(:delete_post, %User{}, %Post{}), do: {:unauthorized}
end

For now on we can call Authorize.check(:delete_post, user, post) to authorize the user to delete the post. it will return {:ok} if user_id of post is equal to id of user.

We can do some refactoring here, because there will be need to authorize many more actions. But remember, this part should be done based on your application's requirements.

defmodule MyApp.Authorize

alias MyApp.Accounts.User
alias MyApp.Posts.Post

 def check(:delete_post, %User{}=user, %Post{}=post), do: authorize_created_by(user, post)


 defp authorize_created_by(%User{id: user_id}, %{user_id: user_id}), do: ok
 defp authorize_created_by(%User{}, _), do: unauthorized

 defp ok, do: {:ok}
 defp unauthorized, do: {:unauthorized}
end

we can latter add

def check(:update_post, %User{}=user, %Post{}=post), do: authorize_created_by(user, post)

to do authorization for updating the post.

That concludes our implementation part. Now we can do some optimizations on the controller end.

sending common error messages on authorization error.

we have a plug in phoenix controller called action_fallback that allows us to call another plug as a fallback action. Follow the examples from https://hexdocs.pm/phoenix/Phoenix.Controller.html#action_fallback/1 to understand how to do that. I don't want to cover it here since it is already well documented. Also as a bonus you have information on how you should call our authorization module in there. So, in our case the FallbackController will have this plug.

def call(conn, {:unauthorized}) do
  # do something and return 403
end

Conclusion

The advantages of following this approach are, It is simple to read and easy to implement. Also it is easy to optimize it further based on your application's business logic.

Posted on by:

Discussion

pic
Editor guide
 

There is confusion at the beginning of the article.
I think you need to replace:

if(user.id == post.id) do

if(user.id == post.user_id) do

 

Thanks, Fixed it