Tuesday, 5 June 2018

Azure AD - Relationships between Azure EA Accounts and Subscriptions

When it comes to relationship between Azure Portal, Azure AD and Azure EA Portal it can get very confusing and frustrating. It can be hard to understand what you have to do to enable authentication in to the Azure Portal and why you have so many Azure ADs flying around. This brief blog post will try and explain relationship between Azure EA Portal account, Azure AD that gets created automatically and Azure subscriptions that can be accessed in the Azure Portal.

When you login to the Azure Enterprise Agreement Portal (Azure EA Portal) you can create "accounts" and under accounts you can create "subscriptions". Subscriptions is where you place your services (this is done in Azure Portal). Accounts is where you place your subscriptions (this is done in Azure EA Portal). When you create "account" Azure AD is automatically created for that account, and all subscriptions under that account link to that Azure AD. Here is the diagram that shows this relationship:

Azure AD, Subscription and Account Relationship Diagram

Let's say you have created ECorpFraud account in the Azure EA Portal and it belongs to ECorp Fraud department. That account will automatically get ECorpFraud Azure AD. This AD will be used to authenticate your users in to Azure Portal and let them see relevant Azure Subscriptions.

Imagine that Alice works in ECorp Fraud department, someone will need to add her to the ECorpFraud AD, once she is in there you will be able to give her permissions to see resources in Subscription A or B. Azure Portal itself uses Azure AD to enable authentication.

Now, if I create ECorp Finance account, same thing will happen. Azure AD for ECorp Finance will be created. I will then be able to add relevant users to that AD and then give them access in to the relevant subscriptions, in this case Alice and John.

Saturday, 26 May 2018

ASP.NET MVC Multitenancy - Introduction & Architecture Overview

Over the past 5 years I had an opportunity to architect and develop multi tenanted applications. However, when it came to ASP.NET C# implementation I have struggled to find examples that solve common problems. In this blog series I will try to provide these examples:

This series focuses on a simple multitenancy architecture (more accessible to wider audience). Here is a high-level overview:

If you would like to get an architectural overview of multitenancy in general and see how you can deploy your application on to Azure then check out my "Developing multi-tenant SaaS business applications on Azure" WROC# conference talk. This talk is very much architectural extension of this blog post series. I hope you will find it useful:

VIDEO IS BEING PREPARED, WILL BE RELEASED SHORTLY...


Sunday, 20 May 2018

ASP.NET MVC Multitenancy, Part 4 - Authorisation

Once users have authenticated in to your application you will need to enable authorisation. If you are building an application that is using federated identity pattern chances are that you are going to be working with claims. ASP.NET comes with Role Based Access Control (RBAC). In this blog post we are going to explore how you can use this with OpenID Connect to enable authorisation.

Identity Provider Token

With OpenID Connect upon authentication users get redirected back to the application with JSON Web Token, this token contains claims about the authenticated user.

JSON Web Token (JWT)

JSON web token looks like this:

   
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayIsImtpZCI6ImlCakwxUmNxemhpeTRmcHhJeGRacW9oTTJZayJ9.eyJhdWQiOiJkYTdhNzQ1Yi05NzUyLTRjNGMtYjI0Zi05YWEyZmVjNGM3N2QiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83NWIwMmUwZC05MGQxLTQzZTUtYjVkYi0yMGVhYWRkYmZhYzYvIiwiaWF0IjoxNTI2ODE5MTM5LCJuYmYiOjE1MjY4MTkxMzksImV4cCI6MTUyNjgyMzAzOSwiYWlvIjoiQVNRQTIvOEhBQUFBREpYd0VWRnJ2WndBcS9WMlBHcjYzeVQybVk5UVYyS0F1MHZqeExGVXp5ST0iLCJhbXIiOlsiSHdkIl0sImZhbWlseV9uYW1lIjoiS2F2dGFza2luIiwiZ2l2ZW5fbmFtZSI6IlphbiIsImlwYWekciI6IjE4NS42OS4xNDQuMTA0IiwibmFtZSI6IlphbiBLYXZ0YXNraW4iLCJub25jZSI6Il9I609zaENvIiwib2lkIjoiODY4OGZlNWYtYjAxZS00MmVkLTk3MWYtN2E0NDkxZWZjMGY1Iiwib25wcmVtX6NpZCI6IlMtMS01LTIxLTE4NzEzNjg5OTctMjU3NzcyMDU0NS02ODMwNjc4MzMtMTA2OTIiLCJzdWIiOiJ4d3JydG5DY2xfS2lrbngxYlJTTzRPem9aTVEwV0V4bmozRmtqXzY2TEVRIiwidGlkIjoiNzViMDJlMGQtOTBkMS00M2U1LWI1ZGItMjBlYWFkZGJmYWM2IiwidW5pcXVlX25hbWU2OiJaYW4uS2F2dGFza2luQG1oci5jby51ayIsInVwbiI6Ilphbi5LYXZ0YXNraW5AbWhyLmNvLnfrIiwidXRpIjoiRnVqeFZFMFAya3lyUEhDTkFJRVdBQSIsInZlciI6IjEuMCJ9.PYi8_x_NJjhPYflU156dy-5XkmTJc_RAu_Gq0C06QHWuoDbBcMAh1TdkulZ7tVq7lF24EY19W965riA0pruz74GWo_9Ny8Jk1MkJi3oBaZQSMrTz_7rc2Km30UJrKKulmM9em5BfT43fMndCDS4fSVMrp8w0ijNgdCzSvTrK9xid0BsJw8hWiMxDdPPYTI_cEzfx0d587knjoamxaizrNsYwkuHuVJvZwITN94j_hV0KW1LBAW-GRtbbNpB7NDi_-GD6dZH3YW6IsipPRBXNxTNMKZ6zjtdQwDCZ8lui1-Gf77tC0cMUGHX_eIgzIAdEIHr5RfZgik2HT1tBJzzq8g

I recommend that you read more about JWT tokens here. When this token is base 64 decoded it will look like this (above token is not valid, so it will not decode):

   
{
  "aud": "fa74745b-8g52-cc4c-bv4f-0aa2fec4casd",
  "iss": "https://sts.windows.net/fa74745b-8g52-cc4c-bv4f-0aa2fec4casd/",
  "iat": 1526819139,
  "nbf": 1526819139,
  "exp": 1526823039,
  "aio": "ADQA3/8HAAZADJXwEVFrvZwAq/V4Pgr63sT2mY9Q42KAu0fjxLFUzyI=",
  "amr": [
    "pwd"
  ],
  "family_name": "Kavtaskin",
  "given_name": "Zan",
  "ipaddr": "182.67.124.103",
  "name": "Zan Kavtaskin",
  "nonce": "_091823kl",
  "sub": "xwrrtnCcl_Kiknx1bRSO4OzoZMQ0WExnj3Fkj_66LEQ",
  "unique_name": "Zan.Kavtaskin@someemail.com,
  "ver": "1.0"
}

Token is essentially a key value property bag, these properties are called claims, these claims are about the authenticated user.

Claims

Claims are simply properties that are trusted (read more about claims here). Now your web app will mainly use these claims for authentication i.e. who is the user, their unique id, etc. It is unlikely that claims will contain user role information (it can happen, however I have not seen this in practice). This is because 3rd party authentication provider normally does not know anything about your application. You can argue that role data is application data and does not belong with the authentication provider. However, ASP.NET needs role claims to perform authorisation checks, what do you do? Well, you append roles to your claims in your application.

ASP.NET MVC Implementation


Appending roles to the claims

   
public class AuthenticationClaimsAppenderMiddleware : OwinMiddleware
{
    public AuthenticationClaimsAppenderMiddleware(OwinMiddleware next)
        : base(next)
    {

    }

    public override Task Invoke(IOwinContext context)
    {
        IUserService userService = ServiceLocator.Resolve<IUserService>();
        ClaimsPrincipal claimsPrincipal = context.Authentication.User;

        string idpID = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier).Value;
        UserDto userDto = userService.GetUserByIdpID(idpID);

        if (userDto == null)
            return this.Next.Invoke(context);

        ClaimsIdentity claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity;

        if (userDto.IsAdmin)
        {
            claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
        }

        return this.Next.Invoke(context);
    }
}

Checking the role claim

Now all you have to do is check the role claim using the authorisation attribute this can be done at the controller and action level:

   
[Authorize(Roles = "Admin")]
public class TenantSettingsController : Controller
{
    //...
}
   
[Authorize]
public class GroupsController : Controller
{
    //...
    [Authorize(Roles = "Admin")]
    [HttpPost]
    public ActionResult RemoveUser(UserModel model)
    {
        //...
    }
}
To enable unauthenticated access use AllowAnonymous attribute:
   
[AllowAnonymous]
public class TenantController : Controller
{
    //...
}
To ensure that user is authenticated without specific role check, use authorize attribute:
   
[Authorize]
public class UserSetupController : Controller
{
    //...
}

*Note: Code in this article is not production ready and is used for prototyping purposes only. If you have suggestions or feedback please do comment.

Saturday, 12 May 2018

ASP.NET MVC Multitenancy, Part 0 - Subdomain VS URL Path

With multitenancy you will need to decide how you are going to identify your tenants. There are two user friendly approaches, subdomain fictionaltenant.multitenantedapp.com or URL Path multitenantedapp.com/fictionaltenant/. In this post we are going to explore disadvantages and advantages of these two approaches.

Subdomain


Configuration

It is surprisingly easy to set this up for a simple deployment model. This is what you have to do:

Setup DNS record

I went to my domain registrar site and configured all my traffic on *.multitenantedapp.com to point to my Azure Web App domain e.g. *.multitenantedapp.com -> zkwildcardtest.azurewebsites.net.


Setup wildcard hostname

After that all I had to do was configure my Azure Web App to accept traffic for the hostname *.multitenantedapp.com. If you are using a web server such as IIS you will just need to change the IIS hostname binding.


Deploy your app

Just to make sure everything works as it should, I have deployed a simple test app that reads the URI and extracts subdomain from it. I went to tenanta.multitenantedapp.com and this is what I saw:


Why should you use this approach?


Advance estate management

For me advance estate management is the main reason why you would go for this approach. As your application scales you will want to split up your deployment zones. For example, you might want to deploy 50 tenants to zone A and other 50 tenants to zone b. What if some tenants in zone A are being noise neighbours (using your application a lot)? How will you migrate them to the zone C? With subdomain you can use DNS to redirect traffic, subdomain will remain the same, just the DNS mapping will need to change.

Customisation

You can enable customisation, so for example if you own fictionalcompany.com domain you can point files.fictionalcompany.com to fictionalcompany.multitenantedapp.com. This sounds simple but there is more to it. You need register this hostname on your web server. Also if you would like your connection to be secure you will need to get fictionalcompany to give you the subdomain SSL certificate.

Log quality

Here is how the subdomain IIS log looks like:

date: 2018-01-30 
time: 22:25:30
s-sitename: ZKWILDCARDTEST
cs-method: GET
cs-uri-stem: /
cs-uri-query: -
s-port: 80
cs-username: -
c-ip: 102.152.31.182
cs(User-Agent): -
cs(Cookie): -
cs(Referer): -
cs-host: fictionalcompany.multitenantedapp.com TENANT'S NAME APPEARS HERE
sc-status: 200
sc-substatus: 0
sc-win32-status: 0
sc-bytes: 1958
cs-bytes: 1066
time-taken: 0

cs-host (client-server host) actually shows fully qualified subdomain. This is great, as it is very easy to parse and hard to spoof. This can be used for business and security analytics.

URL Path


Configuration

There is not much to URL path setup. You just need to configure your web application's framework mapping. If you are using ASP.NET MVC it would look something like this:

  
namespace Web
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            //...

            routes.MapRoute(
                "multi",
                "{tenant}/{controller}/{action}/{id}",
                new {controller = "Dashboard", action = "Index", id = UrlParameter.Optional}
            ).DataTokens.Add("name", "webclient_multitenancy");

        }
    }
}

For full mapping explanation take a look at this article.

With this approach you can introduce proxy layer, that proxy layer can then direct your traffic to different deployment zones. So in a way you can achieve same thing. However you are introducing additional infrastructure, which will require implementation, deployment, hosting and maintenance. If you can avoid it, you should.

Why should you use this approach?

The main benefit is that it is simple to implement and requires no DNS work of any kind. However with this approach you do compromise on advance estate management, customisation and log quality.

Here is how the IIS log would look like with URL path:

   
date: 2018-01-30 
time: 22:25:30
s-sitename: ZKWILDCARDTEST
cs-method: GET
cs-uri-stem: /fictionalcompany/somepage/ TENANT'S NAME APPEARS HERE
cs-uri-query: -
s-port: 80
cs-username: -
c-ip: 102.152.31.182
cs(User-Agent): -
cs(Cookie): -
cs(Referer): -
cs-host: multitenantedapp.com THIS IS NOT SHOWING TENANT'S NAME ANYMORE
sc-status: 200
sc-substatus: 0
sc-win32-status: 0
sc-bytes: 1958
cs-bytes: 1066
time-taken: 0

The main thing to notice is that client server host is now not showing the tenant's name. Now you need to parse cs-uri-stem which is more difficult. Also if your application is a single page application that performs all operations via API, chances are that tenant's name will be in the header which means you will not know which tenant is making the API requests.

Conclusion

If you are starting to work on a large-scale project, I strongly recommend that you give it a bit extra time and invest in the subdomain implementation. It's just so versatile, it will help you scale long term. However if you are working for a small start-up, you don't have the DNS skills in your team and you are not sure where project is going you can just use the URL path to get you off the ground.


*Note: Code in this article is not production ready and is used for prototyping purposes only. If you have suggestions or feedback please do comment.

Sunday, 3 December 2017

ASP.NET MVC Multitenancy, Part 3 - NHibernate Tenant Data Filtering and Shared Database

In part 1 we have established tenant context. In part 2 we have configured authentication. Now we will be getting tenant data out of our RDBMS. Before we start storing and retrieving data it's important that you figure out what kind of multitenancy data segregation model you are going to go for. This is an important point and it really depends on your business requirements. To help you choose check out this Microsoft article: Multi-tenant SaaS database tenancy patterns.

My sample app is called “Stop The Line”, it is very basic. It holds very little data, and I have no idea if it's going to take off or not. I have decided to go with "Multi-tenant app with a single multi-tenant database" pattern. Put simply, this means that all my tenants are going to share the same database and I am going to use foreign key to discriminate between them. This is one the cheapest and fastest ways to implement tenancy.

When it comes to development what I don't want to do is to specify tenant id in each query and I don't want to manually specify tenant id when I create and update my domain objects. Remember the onion architecture, as far as I am concerned tenancy is an infrastructure concern and it should stay out of my domain. After few minutes of searching I have found this great blog post that meets these requirements: Bolt-on Multi-Tenancy in ASP.NET MVC With Unity and NHibernate: Part II – Commingled Data. Let's incorporate this in to my sample app.

Implementation

In my sample app it's possible to query in multitenancy context and without. For example, if a new tenant is signing up for an account then we would not apply the multitenancy context. This is because TenantContext will not exist until tenant was created in the first place.

Also in this implementation I have tried to keep infrastructure decoupled so that it would not be too difficult to move to another database tenancy pattern.

Base Configuration

As there are going to be two different contexts, non-multitenant and multitenant. I have decided to create NHConfiguration base class.

NHConfiguration.cs
   
public abstract class NHConfiguration
{
    public abstract global::NHibernate.Cfg.Configuration Config { get; }
}

Below is the standard configuration that is going to be used for non-multitenant read/write.

Configuration.cs
   
public class Configuration : NHConfiguration
{
    public override NHibernate.Cfg.Configuration Config => config;

    private NHibernate.Cfg.Configuration config;

    public Configuration()
    {
        this.config = Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2012
                .ConnectionString(c => c.FromConnectionStringWithKey("default"))
#if DEBUG
                .ShowSql()
                .FormatSql()
#endif
                .AdoNetBatchSize(50)
            ).Mappings(m =>
                m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())
            )
            .ExposeConfiguration(c => SchemaMetadataUpdater.QuoteTableAndColumns(c))
            .BuildConfiguration();
    }
}

This configuration will get injected into the Unit of Work. If you are new to this, then you should read Unit Of Work Abstraction For NHibernate or Entity Framework C# Example. I have omitted the class implementation.

   
public class NHUnitOfWork : IUnitOfWork
{
    //..
    private NHConfiguration nhConfiguration;

    public NHUnitOfWork(NHConfiguration nhConfiguration)
    {
        this.nhConfiguration = nhConfiguration;
        //..
    }
    //..
}

Unfortunately with this solution you will still need to add the private "tenantId" field to your domain classes.

   
public class Some 
{
    //..
    private Guid tenantId;
}

The good news is that you don't have to do with it anything inside your domain. It will be handled by infrastructure. The bad news is that it still there. I have spent few hours trying to remove it, I have used some reflection and proxy class generation techniques, unfortunately to no avail. If you find an elegant solution please do share it.

Multitenant Writes

This is where standard configuration is expanded and interceptor is set. Interceptor will be used when some object is updated or saved.

ConfigurationMultiTenancy.cs
   
public class ConfigurationMultiTenancy : Configuration
{
    public ConfigurationMultiTenancy(NHSharedDatabaseMultiTenancyInterceptor interceptor)
    {
        this.Config.SetInterceptor(interceptor);
    }
}

Above you will notice that NHSharedDatabaseMultiTenancyInterceptor is injected in to ConfigurationMultiTenancy. Here is the actual interceptor.

NHSharedDatabaseMultiTenancyInterceptor.cs
   
public class NHSharedDatabaseMultiTenancyInterceptor : EmptyInterceptor
{
    readonly TenantContext tenantContext;

    public NHSharedDatabaseMultiTenancyInterceptor(TenantContext tenantContext)
    {
        this.tenantContext = tenantContext;
    }

    public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
    {
        int index = Array.IndexOf(propertyNames, "tenantId");

        if (index == -1)
            return false;

        state[index] = this.tenantContext.ID;

        entity.GetType()
                .GetField("tenantId", BindingFlags.Instance | BindingFlags.NonPublic)
                .SetValue(entity, tenantContext.ID);

        return base.OnSave(entity, id, state, propertyNames, types);
    }
}

Multitenant Reads

Configuring reads is much simpler. You just need to enable a filter and provide the parameter. First you need to define the actual filter.

MultitenantFilter.cs
   
public class MultitenantFilter : FilterDefinition
{
    public MultitenantFilter()
    {
        WithName("MultitenantFilter").AddParameter("TenantId", NHibernateUtil.Guid);
    }
}

Then you need to enable it.

NHUnitOfWorkMultitenancy.cs
   
public class NHUnitOfWorkMultitenancy : NHUnitOfWork
{
    public NHUnitOfWorkMultitenancy(NHConfiguration nhConfiguration, TenantContext tenantContext) 
        : base(nhConfiguration)
    {
        this.Session.EnableFilter("MultitenantFilter").SetParameter("TenantId", tenantContext.ID);
    }
}

Finally you just need to apply it.

   
public class SomeMap : ClassMap<Some>
{
    public SomeMap()
    {
        //..
        //This is used for writes
        Map(Reveal.Member<Some>("tenantId")).Not.Nullable();

        //This is used for reads
        ApplyFilter<MultitenantFilter>("TenantId = :TenantId");
    }
}

*Note: Code in this article is not production ready and is used for prototyping purposes only. If you have suggestions or feedback please do comment.