NB: In this short series of articles, I will refer to a number of fictitious organisations; CloudStruck (www.cloudstruck.com) , Little Pay (www.littlepay.co.uk) and HasToBe.Net (www.hastobe.net). HasToBe.Net is actually my personal blog site that I update only sporadically. CloudStruck and Little Pay are simply domains that I registered for personal use, including creating demonstration material. Their use may change over time, or they may disappear altogether In these examples, Little Pay is our SaaS provider, CloudStruck and HasToBe.Net are its customers.
Identifying the Tenant in Multi-Tenant Azure Applications - Part 2
In part 1 of this article, I outlined some of the key characteristics that I'm looking for when designing a multi-tenant application. I then discussed two potential approaches for identifying the correct tenant within such an application and how they worked for or against these characteristics. In this article, I'm going to take a deeper dive into the next approach, to the extent that we'll see some code samples.
Identify the Tenant Using a Sub-Domain
This approach embeds the tenant identifier in the host name of the URL:
As well as being able to identify the tenant, this is a further improvement because we now have a URL that is branded; our tenant name, CloudStruck, appears directly within the host name. It’s now less obvious that CloudStruck is one of many tenants using a multi-tenanted application. It helps if the fixed part of the DNS name (littlepay.co.uk) is generic and describes the application.
It’s not a major stretch to imagine that this is the payroll site for the underpaid employees of CloudStruck. Returning visitors may well be able to remember the URL, or be able to find it through some lucky URL surfing.
Technically, there are a few things you need to do, both in your application and DNS configuration to make this work.
The first is that your code needs to recognise the tenant from the URL that was used to access your application. Fortunately, this is passed with the HTTP request in the Host Header:
1: public static Tenant GetCurrentTenant(System.Web.HttpRequestBase request)
3: string hostName = GetHostFromHeader(request.Headers["Host"]);
5: return TenantRepository.GetByHost(hostName);
I'll not go into the TenantRepository.GetByHost() method; it's going to specific to your implementation. In mine, it searches for a Tenant record in the Tenant database using this host name. The header will potentially include the port, so be sure to remove it if it's there and make sure your search is case-insensitive, as you can't be sure what case the end-user has used. Also, be prepared to not find a tenant record for the host, if someone has used an invalid tenant-name or has mistyped the address. In that case, either reject the request or redirect to a generic landing page.
The next consideration is how to get requests for <tenant>.littlepay.co.uk to point to your Windows Azure application hosted at <your-application>.cloudapp.net.
The standard way to provide your Windows Azure application with a custom domain name is to use a CNAME DNS record. CNAME defines a DNS alias that maps one DNS host name to another.
Usually, you'd define a straightforward alias in DNS like this:
In the DNS configuration for littlepay.co.uk (which I own), I have this entry:
www CNAME multitenantdemo.cloudapp.net
This creates a DNS alias for www.littlepay.co.uk that points to multitenantdemo.cloudapp.net (my application on Windows Azure).
When a user browses to http://www.littlepay.co.uk, they'll be directed to the IP address
That means that we can define aliases within DNS for every one of our tenants and have all requests for the corresponding URL's directed to our single application on Windows Azure. By examining the Host Header, we can then determine which tenant is being called for and act accordingly.
There is an easier way than creating a DNS CNAME entry for each tenant, though. We can create a wildcard CNAME entry instead, thus:
* CNAME multitenantdemo.cloudapp.net
The wildcard alias will direct <anything>.littlepay.co.uk to our Windows Azure application. This simplifies DNS configuration significantly!
So far, we have a very simple way of configuring DNS and an equally simple piece of application code to identify the tenant once the request reaches the application. If you're using the Windows Azure AppFabric Access Control Service (ACS), we need to tell it:
1. What identity rules to apply for this tenant
2. What URL to come back to once authentication is complete
Fortunately, the answer to both of these is (once again) the DNS domain in the Host Header of the request. If you're using Windows Identity Foundation to handle authentication within your application (and I hope you are), insert the following method into the global.asax.cs file of your Windows Azure Web Role project (I got this code from Wade Wegner).
1: /// <summary>
2: /// Retrieves the address that was used in the browser for accessing
3: /// the web application, and injects it as WREPLY parameter in the
4: /// request to the STS
5: /// </summary>
6: void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
9: // In the Windows Azure environment, override the configured Realm and Reply address
10: // to match the running instance.
12: HttpRequest request = HttpContext.Current.Request;
13: Uri requestUrl = request.Url;
14: StringBuilder wreply = new StringBuilder();
15: wreply.Append(requestUrl.Scheme); // e.g. "http" or "https"
17: wreply.Append(request.Headers["Host"] ?? requestUrl.Authority);
19: if (!request.ApplicationPath.EndsWith("/"))
21: e.SignInRequestMessage.Reply = wreply.ToString();
22: e.SignInRequestMessage.Realm = wreply.ToString();
This method will run whenever Windows Identity Foundation redirects the client to the Identity Provider (in this case, the Windows Azure Access Control Service). It replaces the Realm (the host for which the user is to be authenticated) and the Reply address (the host to which the user will be returned) with the host name from the incoming request. It also handles the case that requests can come over plain HTTP or secure using HTTPS.
All that remains is to configure a Relying Party Application and Rule Group in the Access Control Service for each tenant. What I like about this is that each tenant can have their own authentication scheme, including their corporate Active Directory. For now, I reserve discussion of ACS configuration for another time.
In the final part of this series, we'll look at identifying tenants using custom host names and explore one potential pitfall: secure transfer using HTTPS.
Steve Morgan is Principal Customer Solution Architect / Windows Azure Centre of Excellence Lead Architect at Fujitsu UK&I.