In the world of web development, ensuring the security and privacy of user data is paramount. One of the key aspects of this is implementing robust user authorization systems. In this article, we will delve into the process of building a simple yet effective user authorization system for the Phoenix framework, a system that can be seamlessly integrated into applications of various sizes. Whether you're a seasoned Elixir developer or a beginner just getting started with Phoenix, this guide will provide you with a practical approach to implementing authorization in your applications.
Essentially, authorization allows a user or an entity to perform an action if they have the necessary permissions. 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.
Let's assume we want to authorize a rule where 'only the user who created a post 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're reading this, you're likely seeking a more robust solution.
Authorize module
It is recommended to centralize all authorization logic. 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
From 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 refactor here, as there will be a 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 the implementation part. Now, we can optimize on the controller end.
Sending common error messages upon 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
In conclusion, implementing a user authorization system in Elixir and Phoenix doesn't have to be a daunting task. With the right approach and understanding, you can build a system that is not only simple and easy to read, but also easy to implement and optimize based on your application's business logic. The method we've explored in this article provides a solid foundation, but remember, the world of web development is always evolving. Always be open to exploring new techniques, libraries, and best practices to ensure your authorization system remains robust, secure, and efficient. Happy coding!
Top comments (2)
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