Tuesday, July 23, 2013

Controller MicroArchitecture: Functional Authorization

One common use case for functional authorization is pretty simple:  You can't do function X unless you are signed in.  As I've mentioned in other blogs, I had been following Michael Hartl's tutorial design patterns which included authorization.

Hartl located common authorization functions in the SessionsHelper module.  Sessions being an overused term, it took me a while to understand that there is a blend of Authentication and Authorization in that module.

Authentication caching is handled via cookies and is in these methods:
  • sign_in
  • sign_out
  • current_user
  • current_user=
Functional authorization support is in:
  • signed_in?
  • signed_in_user
    • store_location
    • redirect_back_or
Data authorization support is supplied via:
  • current_user? which compares the current user against a different user (often the owner of certain data)
In each controller, one added the before_action :signed_in_user filter.

A Variation

The Ruby-on-Rails/ActionController overview (section 8) discusses moving the check into the ApplicationController.  For those controllers that don't need signin, one opts out by using :skip_before_action.

Observations

In both approaches, Authorization Policy is distributed to the controllers.  The second design pattern is a little less so but one still needs to check each controller to see if there are any exemptions.
I've also observed both an increase in test complexity and test run-time when authorization is enabled.  Creation of a valid user and sign-in before each test take time.  One also needs to test each method against non-authorized users.

Formalizing Authorization

Like the variant above, I place a filter in my ApplicationController
include AuthorizationHelper ... before_filter do |controller| authorize( controller) end
The ApplicationHelper module:
module AuthorizationHelper def authorize( controller) require_signin = true if( controller.is_a?( MyExemptController) ) then require_signin = false end if( require_signin && !signed_in?) logger.info "Signin required for the #{controller.class}::#{controller.action_name} action" store_location flash[:notice] = "Please sign in." redirect_to signin_url end end end
In this approach, all controllers are unaware of the authentication policy. This allows one to look in one place and see what the intended policy is.
Note that the code makes use of the undocumented .action_name attribute. This can allow per-method control over authorization.

No comments:

Post a Comment