Implementing Dynamic User Routing

Implementing Dynamic User Routing

tutorials

Intro

The Dynamic User Routing article describes a design pattern for routing OAuth requests for end-users to their home region within a global IAM deployment. In this tutorial and video, we will demonstrate how to get an end-to-end solution working.

Get the Code

First clone the GitHub Repository, which provides some Docker resources to enable a fast setup:

GitHub Resources

Understand Components

The example setup uses the following components within a Docker Compose network. The Curity Identity Server will be exposed to the host computer via a reverse proxy, at a base URL of http://localhost:80:

Components

Install Prerequisites

Install Docker Desktop

First, ensure that Docker Desktop is installed and configured with more than the default 2GB of RAM to ensure the cluster has sufficient resources:

GitHub Resources

Install ngrok

OAuth.tools will be used as a test client. In order for it to connect to http://localhost:80, an ngrok tunnel will be automatically created. If you don’t have ngrok installed, this can be done with a command such as brew install ngrok.

Provide a License File

Obtain a license for the Curity Identity Server by signing up on the Developer Portal if needed. The license.json file then must be copied into the idsvr folder of the repository.

Developer Portal

Run the System

View the Docker Compose File

The Docker Compose file runs the following components on a simple network to demonstrate the main ingredients needed for dynamic routing of requests:

Docker Compose

Run All Containers

You can then run the system with one of the following commands, depending on which of these reverse proxies you prefer to use:

Reverse ProxyCommand
NGINX OpenResty./run.sh nginx
Kong Open Source./run.sh kong

The script will generate an internet URL that points to http://localhost:80 and prompt you to copy it for use in OAuth Tools later on:

ngrok prompt

After pressing the enter key to proceed, all containers will start deploying. In addition, OAuth Tools will be opened in a browser window for testing:

Running Containers

Once the Docker system is initialized, select Use Webfinger in OAuth Tools and copy in the ngrok URL to create an environment for testing:

OAuth Tools Environment

Access the System

You can then access the system via the following URLs, though the ngrok generated value will be different in your environment:

ComponentURLDescription
Admin UIhttps://localhost:6749/adminSign in as user admin with Password1 to view the Identity Server configuration
Exposed OAuth Endpointshttps://a072b06f91da.eu.ngrok.io/oauth/v2/oauth-anonymous/.well-known/openid-configurationThe OAuth endpoints that will be used when OAuth Tools is used as a client

The system represents a straightforward global cluster with an admin node, a European runtime node, and an American runtime node:

Global Cluster

Query User Account Data

A user called testuser.eu exists in the European database, and a user called testuser.us exists in the US database. This can be seen by first remoting to a database container:

export USERDATA_EU_CONTAINER_ID=$(docker container ls | grep data_eu | awk '{print $1}')
docker exec -it $USERDATA_EU_CONTAINER_ID bash

Then from within the container’s terminal, issue database queries by pasting in these commands:

export PGPASSWORD=Password1 && psql -p 5432 -d idsvr -U postgres
select username, email from accounts;

Note that if the American user was routed to the below European database, the user would not be found and would experience a login error. The reverse proxy will prevent this from ever happening, though, due to its reliable routing:

Separated User Data

Test Logins

Use a Test Client

In OAuth Tools run a Code Flow with the following settings, to match those supplied in the configuration of the Curity Identity Server instances:

Client Settings

Run Logins as EU and US Test Users

In the early stages of authentication, the user has not yet been identified. Therefore, requests are routed to the default region. In the example setup, this region is the European instance. Credentials are then collected in two stages, starting with a Username Authenticator:

Username Authenticator

Once identified, the authentication workflow continues in the user’s home region so that the user can sign in reliably. Note also that the Username Password Authenticator is being used in Password Only mode, and its user name field is automatically populated from the previous screen:

Verify Credentials

For the American user, a zone transfer action will occur when the Username Authenticator is submitted. This ensures that credentials are verified against the US data source.

View Logs

During processing, you can run one of the following requests to view logs for the reverse proxy:

Reverse ProxyCommand
NGINX OpenResty./logs.sh nginx
Kong Open Source./logs.sh kong

Info-level log statements will be output by the reverse proxy when a zone is detected in either an HTTP Only cookie or a wrapped token:

*** Found zone 'eu' in cookie, client: 172.19.0.1, server: , request: "POST /authn/authentication/UserName-Password HTTP/1.1"
*** Found zone 'eu' in wrapped token, client: 172.19.0.1, server: , request: "POST /oauth/v2/oauth-token HTTP/1.1"
*** Found zone 'eu' in cookie, client: 172.19.0.1, server: , request: "POST /authn/authentication/UserName HTTP/1.1"
*** Found zone 'us' in cookie, client: 172.19.0.1, server: , request: "POST /authn/authentication/UserName-Password HTTP/1.1"
*** Found zone 'us' in wrapped token, client: 172.19.0.1, server: , request: "POST /oauth/v2/oauth-token HTTP/1.1"

View Wrapped Tokens

In OAuth Tools, view authorization codes, access tokens and refresh tokens to see that these are now in JWT format and contain a zone value. Note that these values remain confidential and do not contain any sensitive user data:

{
  iss: http://curity-demo.ngrok.io/oauth/v2/oauth-anonymous
  azp: tools-client
  jti: P$cb4d89ce-0e78-449e-b59e-58d9afd79184
  iat: 1623230431
  exp: 1623230731
  zone: us
}

Reverse Proxy Routing

The reverse proxy’s role is to read the zone from HTTP Only cookies received in front channel requests, or from wrapped tokens received in back channel requests. In both cases, the reverse proxy selects the host address from a map at runtime, which in the example setup looks like this:

Zone ValueMapped Host Address
unknownhttp://internal-curity-eu:8443
euhttp://internal-curity-eu:8443
ushttp://internal-curity-us:8443

This type of routing is possible in any reverse proxy or API gateway with good support for content-based routing. It requires a small amount of code via a simple plugin. For some step-by-step instructions, see the API Gateway Guides for details on how to integrate the routing logic.

--
-- Try to read the zone value from an OAuth request
--
function _M.get_zone_value(config)
  
  if not verify_options(config) then
    return nil
  end
  
  local method = string.lower(ngx.var.request_method)
  if method == 'options' or method == 'head' then
    return nil
  end

  -- First try to find a value in the zone cookie
  local zone = get_zone_from_cookie(config.cookie_name)

  -- Otherwise, for POST messages look in the form body
  if zone == nil and method == 'post' then
    zone = get_zone_from_form(config.claim_name)
  end

  return zone
end

Identity Server Configuration

System Zones

Our example multi-zone system was configured by first defining zones in the System / Zones area:

System Zones

Service Roles

Separate service roles were then created for each zone, and the corresponding zone value is configured under advanced properties of each service role:

Service Roles

Every running instance of the Curity Identity Server is given a service role when it starts, and this is configured for the example instances in the Docker Compose file.

Data Separation

A multi-zone data source is then used to map zones to individual data sources, each of which are PostgreSQL JDBC data sources in the example setup:

Multizone Datasource

Enabling Wrapped Tokens

Next, navigate to Profiles / Token Service / Token Issuers and note that the system is configured to use wrapped tokens:

Wrapped Tokens

Configuring the Zone Claim

A zone claim is configured to be issued in wrapped tokens, and the example setup issues it within the openid scope. The value of this claim is provided by the System Claims Provider, which writes the fixed value of the service role to wrapped tokens:

Zone Claim

Authentication Actions

The tools-client used for testing references a Username Password authenticator that uses a Username Authenticator as a prerequisite:

Username Password Authenticator

The Username authenticator uses two important authentication actions to manage zones, to set the zone value for a user, and to perform a zone transfer:

Username Actions

Set Zone Action

Some logic needs to be provided to associate users to zones. This is done in a script action, which could be implemented in various ways. The example uses a simple option of determining the zone based on a suffix in the user ID, which might exist when using email-based identifiers.

function result(transformationContext) {
    
    var attributes = transformationContext.attributeMap;

    if (attributes.subject.endsWith('.eu')) {
        attributes.zone = 'eu';
    } else {
        attributes.zone = 'us';
    }

    return attributes;
}

A more real-world implementation might instead perform a lookup on a globally replicated data source. This would be a simple map of user IDs to zones and would contain no personally identifiable information.

Zone Transfer Action

The job of the zone transfer action is simply to write a zone cookie, then trigger a redirect to the next stage of authentication processing. This mechanism allows the reverse proxy to read the cookie and re-route the next stage to a server with access to data for the user’s region.

Video Tutorial

The below video provides an interactive walkthrough of the details described in this article, which you can watch to see the solution in action:

Conclusion

Dynamic User Routing with the Curity Identity Server can be implemented fairly easily in reverse proxies and API gateways. This extra layer provides a level of reliability that can be difficult to achieve via infrastructure alone.

With this foundation in place, and with wrapped access tokens sent to your APIs, the reverse proxy routing could be easily extended to perform dynamic routing of API requests. This would enable you to separate your own business data by region when required.

Let’s Stay in Touch!

Get the latest on identity management, API Security and authentication straight to your inbox.

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