How To
ForgeRock Identity Platform
ForgeRock Identity Cloud

How do I use IG (All versions) to proxy a set of applications that all have the same context?

Last updated Feb 23, 2023

The purpose of this article is to provide a solution in IG for proxying a set of applications that share the same context without having to manipulate the request path.


3 readers recommend this article

Overview

The two scenarios that this article will cover are:

  • IG is being used to protect/proxy a set of applications that all live in the same context because they are multiple instances of the same application, for example:
    • http://instance1.example.com/app
    • http://instance2.example.com/app
    • http://instance3.example.com/app
  • IG is being used to protect/proxy a set of different applications that all serve content from the / (root) context, for example:
    • http://app1.example.com/
    • http://app2.example.com/
    • http://app3.example.com/

These two use cases can be solved in the same way since we can think of different applications serving content from the / (root) context as being the same as multiple instances of the same application serving content from the same context, /app for example. In both scenarios, when a request hits IG for any one of these applications, it has to be able to decide which instance of the application (route) should get the request after processing.

One approach is to add a unique path to each request that is sent to IG so that this can be used to decide which proxied application the request is for but this makes for a complex problem of path manipulation to rewrite URLs before and after making the request to the proxied application. This can be achieved using scripting to handle the rewriting but, will likely have a performance impact and can be problematic depending on how well behaved the application being proxied presents/uses links within a given page, relative versus full URLs.

An alternative approach is to use a unique set of fully qualified domain names (FQDNs) to help IG differentiate where the request should be sent.

This article provides two options using FQDNs:

Caution

If a baseURI configuration item is specified in a parent configuration (most likely config.json) then this value will end up replacing aspects of the request.uri value which then means that any conditions expecting to match against the request.uri.host value of the original request won't match. If using this technique, don't set a baseURI value in a parent configuration and set it as required in each route configuration.

Note

The FQDN approach does not require any changes to the FQDN of the application being protected/proxied as the key information that IG needs when contacting the application is held in the baseURI value in the IG configuration which can be based on an IP address, a hostname or a FQDN.

Some applications expect to see a specific host header value but this is easy to add/change using the HeaderFilter.

Using Unique FQDNs

When IG is being used to protect/proxy multiple applications it needs to know which route to activate and this is controlled using a boolean condition expression in the route. Using the FQDN approach, we are able to use the host value from the request URI as part of the condition for a route, for example:

"condition": "${request.uri.host == 'instance1.ig.example.com'}"

Based on a typical flow UserAgent -> IG -> App Instance 1 -> IG -> UserAgent and using the FQDN approach we end up with requests to IG like:

UserAgent (https://instance1.ig.example.com/app) -> IG (condition logic to determine route to use) -> http://instance1.example.com/app -> IG -> UserAgent

The proxied application may return a 302 Location (redirect) header in a response that is designed to take the user to another part of the site, to send the user to log in for example. If the location header includes the full URL of the proxied application rather than just a relative URI then we can use the LocationHeaderFilter to handle those cases and change the URL in the header before it is returned to the UserAgent.

Example setup

Using multiple instances of the IG sample application running in a local setup listening on different ports (7081 and 8081), a single instance of IG configured to listen on port 7070 and a couple of FQDN aliases pointing to the same IP where instance1.ig.example.com and instance2.ig.example.com can be mapped onto 127.0.0.1 when it is all running locally. Additionally, this example setup retrieves a secret from an environment variable as detailed in: SystemAndEnvSecretStore.

Think of this as a mix of the multiple instances of the same application and applications that serve all their content from the same context, the / (root) context in this case.

Example flows

The following examples show how the FQDN approach can be used for 2 different routes, one for each FQDN:

{    "condition": "${request.uri.host == 'instance1.ig.example.com'}",     "name": "Instance 1",     "baseURI": "http://localhost:7081",     "handler": {         "type": "Chain",         "name": "Instance1Chain",         "config": {             "filters": [                 {                     "type": "LocationHeaderFilter",                     "name": "Instance1LocationHeaderFilter"                 }             ],             "handler": {                 "type": "Delegate",                 "name": "Instance1Delegate",                 "config": {                     "delegate": "ClientHandler"                 },                 "capture": "all"             }         }     },     "capture": "all" } {    "condition": "${request.uri.host == 'instance2.ig.example.com'}",     "name": "Instance 2",     "baseURI": "http://localhost:8081",     "handler": {         "type": "Chain",         "name": "Instance2Chain",         "config": {             "filters": [                 {                     "type": "LocationHeaderFilter",                     "name": "Instance2LocationHeaderFilter"                 }             ],             "handler": {                 "type": "Delegate",                 "name": "Instance2Delegate",                 "config": {                     "delegate": "ClientHandler"                 },                 "capture": "all"             }         }     },     "capture": "all" }
  • Making a request to http://instance1.ig.example.com:7070/home is passed on to http://localhost:7081/home: [qtp96639997-47] INFO o.f.o.d.c.C.c.top-level-handler @instance1 - --- (request) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1191 ---> GET http://instance1.ig.example.com:7070/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Cache-Control: max-age=0 Connection: keep-alive Host: instance1.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [qtp96639997-47] INFO o.f.o.d.c.C.c.Instance1Delegate @instance1 -  --- (request) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1191 ---> GET http://localhost:7081/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Cache-Control: max-age=0 Connection: keep-alive Host: instance1.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [I/O dispatcher 14] INFO o.f.o.d.c.C.c.Instance1Delegate @instance1 -  <--- (response) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1191 --- HTTP/1.1 200 OK Content-Length: 9519 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=6684632267997185021; Path=/ [entity] [I/O dispatcher 14] INFO o.f.o.d.c.C.c.top-level-handler @instance1 -  <--- (response) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1191 --- HTTP/1.1 200 OK Content-Length: 9519 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=6684632267997185021; Path=/ [entity]
  • And a request to http://instance2.ig.example.com:7070/home (the same IG as before) is passed on to http://localhost:8081/home: [qtp96639997-61] INFO o.f.o.d.c.C.c.top-level-handler @instance2 - --- (request) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1211 ---> GET http://instance2.ig.example.com:7070/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Connection: keep-alive Cookie: session-cookie=2482869196511153843 Host: instance2.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [qtp96639997-61] INFO o.f.o.d.c.C.c.Instance2Delegate @instance2 -  --- (request) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1211 ---> GET http://localhost:8081/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Connection: keep-alive Cookie: session-cookie=2482869196511153843 Host: instance2.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [I/O dispatcher 12] INFO o.f.o.d.c.C.c.Instance2Delegate @instance2 -  <--- (response) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1211 --- HTTP/1.1 200 OK Content-Length: 9582 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=7810405693614606983; Path=/ [entity] [I/O dispatcher 12] INFO o.f.o.d.c.C.c.top-level-handler @instance2 -  <--- (response) id:f4d2ee36-dde3-47e9-a28e-ad2b928af84c-1211 --- HTTP/1.1 200 OK Content-Length: 9582 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=7810405693614606983; Path=/ [entity]

Using the DispatchHandler

When using IG to protect/proxy multiple instances of the same application you might find the route files become repetitive with items that might make sense to share across all of the instances, like SSO using AM or OAuth2/OIDC. In that scenario, using the DispatchHandler might be a worthwhile alternative, the key thing is what common value or regex can be used in the route's condition value.

Example Setup

As in the example above, this is using multiple instances of the IG sample application running in a local setup listening on different ports (7081 and 8081), a single instance of IG configured to listen on port 7070 and a couple of FQDN aliases pointing to the same IP where instance1.ig.example.com and instance2.ig.example.com are mapped onto 127.0.0.1 in this case. Additionally, this example setup retrieves a secret from an environment variable as detailed in: SystemAndEnvSecretStore.

The condition is triggered based on a set of FQDNs with a regex pattern of:

  • IG 7.1.2 and later: "condition": "${find(request.uri.host, 'instance[0-9].ig.example.com')}",
  • Pre-IG 7.1.2: "condition": "${matches(request.uri.host, 'instance[0-9].ig.example.com')}",

Example flows

The following example shows how the FQDN approach can be used in a single route using a DispatchHandler (example applies to IG 7.1.2 and later):

{    "heap": [         {             "type": "Chain",             "name": "InstancesChain",             "config": {                 "filters": ["InstancesLocationRewrite"],                 "handler": "CaptureClientHandler"             }         },         {             "type": "Chain",             "name": "InstancesChainWithSSO",             "config": {                 "filters": ["SingleSignOn", "InstancesLocationRewrite"],                 "handler": "CaptureClientHandler"             }         },         {             "type": "ClientHandler",             "name": "CaptureClientHandler",             "capture": "all"         },         {             "type": "LocationHeaderFilter",             "name": "InstancesLocationRewrite"         },         {             "name": "AmService",             "type": "AmService",             "config": {                 "url": "https://am.example.com:8443/am",                 "agent": {                   "username": "ig_agent",                   "passwordSecretId": "agent.secret.id"                 },                 "version": "7",                 "notifications": {                   "enabled": true                 }             }         },         {             "type": "SingleSignOnFilter",             "name": "SingleSignOn",             "config": {                 "amService": "AmService"             }         }     ],     "name": "systemandenvsecret",     "secrets": {     "stores": [       {         "type": "SystemAndEnvSecretStore",         "config": {           "format": "PLAIN"         }       }     ]   },     "condition": "${find(request.uri.host, 'instance[0-9].ig.example.com')}",     "name": "allinstances",     "handler": {         "type": "DispatchHandler",         "name": "InstancesDispatchHandler",         "config": {             "bindings": [                 {                     "condition": "${request.uri.host == 'instance1.ig.example.com'}",                     "baseURI": "http://localhost:7081",                     "handler": "InstancesChainWithSSO"                 },                 {                     "condition": "${request.uri.host == 'instance2.ig.example.com'}",                     "baseURI": "http://localhost:8081",                     "handler": "InstancesChain"                 },                 {                     "handler": {                         "type": "StaticResponseHandler",                         "name": "InstanceNotConfigured",                         "config": {                             "entity": "<html><body>No instance configured for host ${request.uri.host}",                             "status": 404,                             "reason": "InstanceNotFound",                             "headers": {                                 "content-type": ["text/html"]                             }                         }                     }                 }             ]         }     },     "capture": "all" }
  • Making a request to http://instance2.ig.example.com:7070/home is passed on to http://localhost:8081/home: [qtp96639997-20] INFO o.f.o.d.c.C.c.top-level-handler @instances - --- (request) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-25 ---> GET http://instance2.ig.example.com:7070/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Cache-Control: max-age=0 Connection: keep-alive Host: instance2.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [qtp96639997-20] INFO o.f.o.d.c.C.c.CaptureClientHandler @instances -  --- (request) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-25 ---> GET http://localhost:8081/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Cache-Control: max-age=0 Connection: keep-alive Host: instance2.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [I/O dispatcher 51] INFO o.f.o.d.c.C.c.CaptureClientHandler @instances -  <--- (response) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-25 --- HTTP/1.1 200 OK Content-Length: 9519 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=8971414396973713320; Path=/ [entity] [I/O dispatcher 51] INFO o.f.o.d.c.C.c.top-level-handler @instances -  <--- (response) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-25 --- HTTP/1.1 200 OK Content-Length: 9519 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=8971414396973713320; Path=/ [entity]
  • Then making a request to http://instance1.ig.example.com:7070/home is passed on to http://localhost:7081/home with the additional SSO filter: [qtp96639997-93] INFO o.f.o.d.c.C.c.top-level-handler @instances - --- (request) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-27 ---> GET http://instance1.ig.example.com:7070/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Connection: keep-alive Cookie: session-cookie=4519971974716775492 Host: instance1.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [qtp96639997-93] INFO o.f.o.d.c.C.c.top-level-handler @instances -  <--- (response) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-27 --- HTTP/1.1 302 Found Location: https://am.example.com:8443/am/?goto=http://instance1.ig.example.com:7070/home&realm=/ [qtp96639997-19] INFO o.f.o.d.c.C.c.top-level-handler @instances -  --- (request) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-29 ---> GET http://instance1.ig.example.com:7070/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Connection: keep-alive Cookie: session-cookie=4519971974716775492; amlbcookie=01; iPlanetDirectoryPro=gMbkOVnS_i0IPUab2t464Cy5stw.*AAJTSQACMDEAAlNLABwwa1hoZENUdGx2SUlmV3Zuc0ZZdXFmMmh3VG89AAJTMQAA* Host: instance1.ig.example.com:7070 Referer: https://am.example.com:8443/am/XUI/?goto=http://instance1.ig.example.com:7070/home&realm=/ Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [I/O dispatcher 12] INFO o.f.o.d.c.C.c.CaptureClientHandler @instances -  --- (request) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-29 ---> GET http://localhost:7081/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Connection: keep-alive Cookie: session-cookie=4519971974716775492; amlbcookie=01; iPlanetDirectoryPro=gMbkOVnS_i0IPUab2t464Cy5stw.*AAJTSQACMDEAAlNLABwwa1hoZENUdGx2SUlmV3Zuc0ZZdXFmMmh3VG89AAJTMQAA* Host: instance1.ig.example.com:7070 Referer: https://am.example.com:8443/am/XUI/?goto=http://instance1.ig.example.com:7070/home&realm=/ Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [I/O dispatcher 52] INFO o.f.o.d.c.C.c.CaptureClientHandler @instances -  <--- (response) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-29 --- HTTP/1.1 200 OK Content-Length: 9977 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=1452732844354872133; Path=/ [entity] [I/O dispatcher 52] INFO o.f.o.d.c.C.c.top-level-handler @instances -  <--- (response) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-29 --- HTTP/1.1 200 OK Content-Length: 9977 Content-Type: text/html; charset=ISO-8859-1 Set-Cookie: session-cookie=1452732844354872133; Path=/ [entity]
  • Then making a request to http://instance3.ig.example.com:7070/home, which has not been configured, will trigger the default condition: --- (request) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-43 ---> GET http://instance3.ig.example.com:7070/home HTTP/1.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.5 Cache-Control: max-age=0 Connection: keep-alive Cookie: amlbcookie=01; iPlanetDirectoryPro=gMbkOVnS_i0IPUab2t464Cy5stw.*AAJTSQACMDEAAlNLABwwa1hoZENUdGx2SUlmV3Zuc0ZZdXFmMmh3VG89AAJTMQAA* Host: instance3.ig.example.com:7070 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0 [qtp96639997-117] INFO o.f.o.d.c.C.c.top-level-handler @instances - <--- (response) id:1c32eca0-d141-47ee-a2a9-189c3d0ae1ab-43 --- HTTP/1.1 404 Not Found Content-Length: 68 content-type: text/html [entity]

See Also

Installing and configuring IG

Getting started

Gateway guide

Reference

Related Training

N/A

Related Issue Tracker IDs

N/A


Copyright and Trademarks Copyright © 2023 ForgeRock, all rights reserved.