User Routing in Cloud Platforms

User Routing in Cloud Platforms

tutorials

Intro

This tutorial discusses options for performing dynamic user routing in cloud platforms, as part of the overall solution from Implementing Dynamic User Routing.

Windows Azure will be used as an example provider, though the principles are the same in any cloud system. Key requirements are summarized below:

OAuth Message TypeRouting Requirement
Front Channel RequestsRoute requests to a region based on the user’s zone from an HTTP Only cookie
Back Channel RequestsRoute requests to a region based on the user’s zone claim from a wrapped token

Load Balancer Routing

Cloud providers have Multiple Load Balancing Options, and some of these have fairly good routing capabilities including the ability to route based on custom headers, which may include cookies.

Cloud Reverse Proxy Routing

For the more complete content based routing that is needed, it is common for cloud platforms to offer more specialist reverse proxies such as Azure NGINX Plus. Some of these options are run with a Platform as a Service (PAAS) model, so that you can avoid the need to manage any actual servers or containers.

Cloud API Gateways

Cloud platforms also offer specialist API Gateways that most commonly sit in front of APIs, to implement various cross cutting concerns.

Some of these have good support for content based routing and could be used to implement dynamic routing of OAuth requests, whereas others may lack the required features.

In this article Azure API Management will be used to implement OAuth request routing. This has some good extensibility via C# Policy Expressions, which enable quite powerful routing when needed.

Create an API Management Resource

Sign in to the Azure Portal and create an API Management Resource for one or more regions. In the following screenshot a resource called curity-example was created in a European region.

This would make the Curity Identity Server available at https://curity-example.azure-api.net, though in a real setup you would typically also create a custom domain.

Configure Proxying of OAuth Requests

Next, create an ‘API’ entry with a fixed back end URL, and in Settings disable the requirements for a Subscription Key to be included in incoming requests:

Next, define operations to proxy all requests through to the Curity Identity Server, and this should be done for the full set of HTTP methods:

Azure Proxying

Configure Routes via Policy Expressions

Next, navigate to Inbound Processing settings for the API and update the XML to define the routing rules. A set-variable expression will calculate the zone value from the incoming HTTP request, though initially it is returning an empty value.

Separate regional base URLs are defined via a set-backend-service expression that reads the zone variable value. In an Azure setup these URLs would point to deployed instances of the Curity Identity Server.

<policies>
    <inbound>
        <base />
        <set-variable name="zone" value="@{ 
            return "";
        }" />
        <set-backend-service base-url="https://internal-curity-eu:8443" />
        <choose>
            <when condition="@(context.Variables.GetValueOrDefault<string>("zone") == "eu")">
                <set-backend-service base-url="https://internal-curity-eu:8443" />
            </when>
            <when condition="@(context.Variables.GetValueOrDefault<string>("zone") == "us")">
                <set-backend-service base-url="https://internal-curity-us:8443" />
            </when>
        </choose>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Read the Zone in OAuth Requests

The set-variable expression then needs to be implemented via C# code snippets, which can use built in libraries. Resources such as the C# Policy Expression Cheat Sheet explain common use cases.

The full C# code is shown below, which looks for a zone in an HTTP Only cookie or a wrapped token. The logic is identical to that coded in LUA for the NGINX and Kong reverse proxies:

var zoneCookieName = "zone";
var zoneClaimName = "zone";
var defaultZone = "eu";

var rawCookie = context.Request.Headers.GetValueOrDefault("cookie", "");
string[] cookies = rawCookie.Split(new string[]{",", ";"}, StringSplitOptions.None);
string zoneCookie = cookies.FirstOrDefault( c => c.Trim().Contains($"{zoneCookieName}="));
if (zoneCookie != null) {
    return zoneCookie.Split('=')[1];
}

if (context.Request.Method.ToUpper() == "POST") {

    var body = context.Request.Body.As<String>();
    if (!String.IsNullOrWhiteSpace(body)) {

        string[] fields = body.Split('&');
        var form = new Dictionary<String, String>();
        foreach (string field in fields)
        {
            string[] parts = field.Split('=');
            form.Add(parts[0], parts[1]);
        }

        if (form.Count > 0) {

            string wrappedToken = "";
            string grantType;
            if (form.TryGetValue("grant_type", out grantType)) {

                if (grantType == "refresh_token") {

                    string refreshToken;
                    if (form.TryGetValue("refresh_token", out refreshToken)) {
                        wrappedToken = refreshToken;
                    }
                }
                else if (grantType == "authorization_code") {

                    string code;
                    if (form.TryGetValue("code", out code)) {
                        wrappedToken = code;
                    }
                }
                else {

                    string token;
                    string state;
                    if (form.TryGetValue("token", out token) && !form.TryGetValue("state", out state)) {
                        wrappedToken = token;
                    }
                }
            }

            if (!String.IsNullOrWhiteSpace(wrappedToken)) {

                var zoneClaim = wrappedToken.AsJwt()?.Claims[zoneClaimName].FirstOrDefault();
                if (!String.IsNullOrWhiteSpace(zoneClaim)) {
                    return zoneClaim;
                }
            }
        }
    }
}

return defaultZone;

Test User Logins

Once the Azure work is complete you would update the runtime base URL configured in the Curity Identity Server, to match the internet URL of the Azure API Management resource.

Then test the end-to-end flow for your deployed instances, following a similar process to that in Implementing Dynamic User Routing, to ensure that users from all regions are able to login successfully.

Conclusion

A general approach for implementing OAuth dynamic user routing in a cloud platform was described, where the two main options are to use a reverse proxy or the platform’s API gateway. We then showed how to implement an Azure specific solution, using C# for the routing logic.

Keep up with our latest articles and how-tos RSS feeds.