Rails Admin and Multitenancy

Rails Admin vs. Multitenancy

Recently I started changing our single-tenant app into a multi-tenant one. Everything was going well until I stuck upon Rails Admin. As we all know, Rails Admin is a great tool to administer your data, among other gems like Administrate or Active Admin.

The challenge with Rails Admin I had was about scoping down the data so that given user sees data only from his tenant. I saw the issue on Github and unfortunately Rails Admin by default does not have the ability to add scopes with parameters. For instance, right now it is impossible to create an ActiveRecord scope to which we pass current user:

scope :for_current_user, ->(user) { where(user: user) }

Exploring Rails Admin Code

I walked through the code. Firstly I tried adjusting the dashboard and index actions so they support passing the current user argument to the scope. After rethinking the problem, I took a different approach.

While reading the code, I saw that Rails Admin supports two kinds of authorizations: CanCan and Pundit. That gave me an idea that maybe instead of adjusting the gem to handle the additional argument, I could create my logic for authorization. In turn, I started reviewing the CanCan authorization logic.

Fortunately. The authorization logic was quite easy to grasp and well documented. Finally being inspired by the CanCan implementation, I created my solution suited for multi-tenancy.

Implementation of Multi-Tenant Authorization

Here are the steps to set up Rails Admin supporting multi-tenancy:

1. Create a module with class AuthorizationAdapter, which contains logic for authorization:

module MultiTenantAuthorization
  class AuthorizationAdapter
    def initialize(controller)
      @controller = controller
    end

    def authorized?(_action, _abstract_model = nil, model_object = nil)
      return unless @controller.current_user.admin?
      return true unless model_object
      model_object.tenant == @controller.current_user.tenant
    end

    def query(_action, abstract_model)
      abstract_model.model.for_tenant(@controller.current_user.tenant)
    end
  end
end

In my particular case, I had to limit the data scope by checking if given model is in user tenant.

2. Add a multitenant extension to Rails Admin:

RailsAdmin.add_extension(:multi_tenant_authorization, MultiTenantAuthorization, authorization: true)

3. Use the authorization in Rails Admin config:

RailsAdmin.config do |config|
  config.authorize_with :multi_tenant_authorization
end

Implementing multi-tenant authorization class allows limiting and protecting other users data.

Back