Blog

Multi-Tenant Applications: Detecting the Tenant

To (finally) continue with my blog series on multi-tenant web applications, let's first discuss detecting the tenant. (If you'd like to recap, please check out the Introduction and What is a multi-tenant application posts.)

Examples in this post (and series) will focus on examples written for Ruby on Rails and/or Rack applications.

Problem

In a multi-tenant app, each request that comes in can be for a separate tenant. We need two things from the outset: a way to determine what tenant a request is for, and a way to process the request in the context of that tenant.

Unfortunately, the solution isn't clear-cut, and depends substantially on the business needs of the application.

Example: Siloed data within one application

Many applications, such as BaseCamp silo their tenants' data, and a user logged into Company X's BaseCamp account can't mingle X's data with Company Y's. But the user still knows they're going to BaseCamp and using that application. Assuming that each user belongs to one tenant, we could easily use the logged-in user as our determining factor.

Example: White-labeled app

Suppose your application is completely white-labeled. Individual users can't know that Tenant A's site and Tenant B's are really powered by the same backend that you're building. In a case like this, the tenant needs to be distinguished from the URL alone, so that the application can always serve customized content, even for requests from users that aren't logged in, or maybe don't have an account. For partial white-labeling, we could distinguish on subdomain of the request (e.g. tenant_slug.myapp.com, or a parameter after the domain (www.myapp.com/tenant_slug). To fully white-label, we'd have to point each of our tenants' unique domains at our application, perhaps using a subdomain (e.g. www.tenant_one.com or myapp.tenant_two.com, depending on their needs). Obviously this has the downside of requiring changes with the tenant's domain registrar, whereas we could use a wildcard subdomain record on myapp.com to get the previous approach.

Solution

Of course the solution depends greatly on your specific needs, but the two examples above cover the common cases pretty well.

Single login leading to tenants

If, as in the first example above, "tenancy" is more about siloing data than providing a completely custom experience, then the solution is quite simple.

It's common in Rails applications to have a current_user helper in ApplicationController; simply add a current_tenant helper that looks like

def current_tenant
  current_user.tenant
end

(or something like that, as appropriate to your data model).

You might be tempted to have deeply-nested routes in your RESTful application, like /tenant/:tenant_id/model_name/:model_id. But given that a user belongs to a single tenant, the /tenant/:tenant_id portion of the route is both redundant and misleading: it gives the user the impression that they could change the tenant_id and get different data. (For obvious security reasons, hopefully that's not the case!) Instead, simplify your routes to /model_name/:model_id and determine the tenant from the user. They'll never go wandering off to another tenant, not even accidentally.

Route-based tenant detection for white-label apps

In case you aim to deliver a more comprehensive tenant experience, where the user isn't aware (or is minimally aware) of the fact that they're using a multi-tenant app, you have a couple of choices.

Detection in controller

One is to look at the request object in your controllers. Your controller might look like

class PublicController < ApplicationController
  before_filter :find_tenant

  …

  private

  def find_tenant
    @tenant = Tenant.find_by_slug request.subdomain
  end
end

I've used this in hybrid applications that use tenant subdomains for certain public routes, but determine by logged-in user for most of the application. However, I wouldn't recommend it for a fully white-labeled app; you'll need to use this in too many different places. (And it may not be appropriate for your data storage system, as we'll discuss later in the series.)

Early detection in Rack middleware

To solve the problem of multi-tenancy in a white-labeled app with independent data storage, we turned to Rack.

Let's look at the middleware itself (stripped down to just detection code):

module Rack
  class MultiTenant
    def initialize(app)
      @app = app
    end

    def call(env)
      request = Rack::Request.new(env)
      # CHOOSE ONE:
      domain = request.host               # switch on domain
      subdomain = request.subdomain       # switch on subdomain

      @tenant = TENANT_STORE.fetch(domain)

      # Do some configuration switching stuff here ...

      @app.call(env)
    end
  end
end

You'll notice the TENANT_STORE "hash" in the above code. This could be an actual hash that's somewhere in your application code, but that's not very dynamic and requires a code change (and application restart) to modify. Instead, that code should be replaced with something that can change during runtime – perhaps a tenants table in your database, or a hash in Redis.

To add this to your app, add the following line to config/application.rb:

config.middleware.use 'Rack::MultiTenant'

Stay tuned ….

Now that our app can detect the tenant for a request, we need to do something about it. Next time, we'll discuss strategies for managing SQL data in a multi-tenant app.