SaaSBits 02 - Authentication
Continuing on Building SaaS solution journey. The past articles are here:
- Part 1: http://blogs.msdn.com/b/bursts_of_random_architecture_thoughts/archive/2015/02/19/saas-bits-01.aspx
We have layered the foundation architecture where we decided
All requests will be directed to a gateway (reverse proxy, based on IIS ARR). Previous code (https://github.com/khenidak/SaaSBits check 2015-2-19 directory) redirect to one of 2 URLs statically defined as didn’t build the scale part yet.
Based on user information (and dynamic information managed by the capacity and scale component CSC) the request will be routed.
The gateway/router routs to a “pod”. The number and the addresses of pods is managed by the CSC.
Based on our requirements request is redirected to a certain pod based on:
Tenant (request belongs to a premium tenant which is served by a dedicated pod)
If not, then the tenant decided by the CSC which can be scaling or compacting (un-provisioning pods) the system.
A bit of side info on the need for scale/compacting approach:
If you keep the cost of building/upgrading your SaaS software aside you will notice that the majority of cost is running/operating cost (i.e the number of active VMs, roles and other resources). Hence the need to keep those as minimum as possible (less - paid for - compute + storage resources serving the same amount of users means more financial yields of the system).
So where should the gateway look for user’s tenant (per request? The obvious answer is authentication claim (from the AuthN system). As in the Gateway will have to broker the AuthN for each request before it routes it to a pod. The rest of authentication requirements (aside from the obvious ones) are:
Federate: allow SaaS customers to use their identity providers.
Standardize: no funky AuthN or proprietary mechanisms that doesn’t work with your customers.
Transparency: you don’t want to change the actual authentication tokens. This to allow SaaS application developers to make use – AS IS without the need to change - of the fun stuff like ADAL, WIF or OWIN AuthN middleware. With scenarios like impersonation/on-behalf-of enabled.
A windows Azure A/D WAAD tenant (core tenant) will be created where
SaaS apps (i.e hr.cloudapp.net> or erp.cloudapp.net) will be published as a multi-tenant application (follow these instructions for how, you will need a verified domain on WAAD for multitenant applications). With its redirect URL points to the gateway (more on this later).
SaaS gateway/router will be also published to this tenant but not as a multi-tenant. It will carry the URLs (via cname records) the URLs which you will share with your customers (ie hr.<yourcompany>.com erp.<your company>.com).
Each of our customers will have a WAAD tenant as the following:
Option1: customer’s self-operated tenant is federated with ADFS (or similar) which authenticates based on an on-prem A/D.
Option 2: customer’s self-operated tenant is federated with ADFS which authenticates using a custom user store.
Option 3: customer’s self-operated tenanted is synchronized with an on-prem A/D.
Option 4: for customers who don’t have a WAAD we can create and operate a tenant for each (this option is also good if we already have an on-prem software and we are converting it to cloud based SaaS).
WAAD will be providing a single token format for our SaaS applications as the following:
oAuth2 JWT for web API apps
OpenID-Connect for Web Applications
The above pretty much covers most of the authentication and token formats currently available in the industry.
The authentication for the entire system code will work as the following
The gateway is an IIS application that has the following:
Custom HTTP module:
Identify if the target request is for a web app or web API
For Web App: Checks the OpenID-Connect authentication token (manaually as a multitenant app) extracting the TenantID and UPN into HTTP header.
For Web API: checks oAuth bearer token (this code is not implemented yet).
Appends a custom oAuth token to a custom header (it authenticates itself as a daemon check: https://msdn.microsoft.com/en-us/library/azure/dn646737.aspx#BKMK_Daemon).
The IIS ARR components uses the tenant Id and user principal to decide on routing (currently static routing, dynamic routing is not implemented yet).
The backend application will be configured as usual in addition to a preceding component (executes before regular AuthN code) to validate the custom gateway oAuth (from the custom header). This ensure that these applications will only be called from the gateway.
What is in the Code:
The entire code is abstracting IIS functionality. The code is future ready for ASP.NET 6 where System.Web will not be used. Also the abstraction allows you to run the code away from IIS if needed.
Note HTTPs not implement yet. So all tokens used in testing is shared on unencrypted channel.
An azure web site sample application configured with default OWIN OpenID-Connect and a custom empty OWIN component that will be used as gateway authenticator.
A web API application (as azure web site).
A custom deployment replicator PowerShell script that deploys the azure web sites multiple times to simulate pods.
Deploy the gateway as Web Role (already configured, just update with your key). The package already performs all the installation needed via startup tasks.
Create WAAD tenant (core tenant) with a verified domain ie saasdomain.com
Publish gateway application to your tenant as a regular web app/web API.
Publish gateway application as a client in your core tenant. (the oAuth bearer token is based on the gateway as an application and a client).
Publish a sample SAAS backend app ie hr.saasdomain.com as a multitenant web app/web api.
On your DNS (or RDP into the gateway web role and modify hosts file). Create a CNAME of hr.saasdomain.com pointing to the gateway ur lie gateway.cloudapp.net.
Create another WAAD tenant (as a customer tanant) add a test user to it.
Open your favorite browser browse to hr.saasdomain.com sign in to WAAD you will be redirected to the gateway (where the above process executes).
Review the entities statically loaded in GatewayApp class in SaaS.Gateway.Common assembly
Publish sample backend applications (not currently needed).
Code has been published here: