In a web framework, when we handle a request in our application, we always do the same things, every time:
- Authenticate the user
- Authorize the request
- Validate the request
- Handle business logic
- Persist the request
- Handle HTTP overhead
- Render some response
The authorization function determines whether a particular entity is authorized to perform a given activity.
For our function to make the right decision, there are three things to consider:
- Who is the
current_user
- What controller/method is being authorized
- What data the request is going to manipulate
class Post < ActiveRecord::Base
attr_accessor :current_user
validate :current_user_is_admin
def current_user_is_admin
unless current_user.role == 'admin'
errors.add(:current_user, 'must be an admin')
end
end
end
- You can no longer save Post models outside of a web form. (tests, console, background jobs, etc.)
- Fat models, no separation of concerns
before_action :redirect_non_admin, only: [:flip_paid]
before_action only: [:show, :edit, :update, :destroy] do
redirect_non_owner Order.find(params[:id])
end
before_action only: [:show, :edit, :update, :destroy] do
redirect_different_organization Order.find(params[:id])
end
- Produce spaghetti code very easily
- Hard to test
- Queries the database multiple times
def update
user = User.find(params[:id])
unless user.supervisor == current_user
raise "Trying to edit unauthorized user!"
end
if params[:user][:role] == 'admin'
raise "Trying to set unauthorized value!"
end
user.update_attributes(params[:user])
end
- Authorization logic is spread throughout the application
- Crazy to test
- Changing authorization rules means reviewing all code and tests
def update
post = Post.find(params[:id])
authorize! current_user, post, :update
post.update_attributes(params[:user])
end
- Controllers don't know about authorization, but they enforce it
- Authorization logic is grouped in one single place
- Can use helpers in the views
- Testable