How To
ForgeRock Identity Platform
ForgeRock Identity Cloud

How do I use the baseURI and originalURI in IG 6.x and 7.x?

Last updated Jun 8, 2021

The purpose of this article is to provide more detailed information on the baseURI and originalURI, and their relationship to the Request URI in IG routes and downstream applications.


1 reader recommends this article

Introduction

The BaseUriDecorator plays an important part within the IG configuration as it provides a mechanism to rebase the request hitting IG to match the URI of a downstream application. Rebasing in this context refers to changing only the scheme://host:port values of the Request URI. By default, IG makes a BaseUriDecorator called baseURI available, and it is used in all the examples demonstrated in this article. See BaseUriDecorator for further information.

Applying the baseURI decorator does not change other aspects of the request, such as headers, path, or parameters. If this is required, then the UriPathRewriteFilter can be used to manipulate request paths, and the HeaderFilter can be used to manipulate headers. More sophisticated use-cases can be handled using Scripts. See the following links for further information:

The simplest example of rebasing the URI is IG just acting as a proxy for all requests it receives: User-Agent → IG → Downstream Application

http://ig.internal.example.com:8080/simple → IG (top-level-handler → route → “baseURI”: “http://app.internal.example.com:7070” → ReverseProxyHandler) → http://app.internal.example.com:7070/simple

Route Considerations 

It is important to note that once the baseURI decorator is applied, the Request URI changes from that point on, so any route conditions or filters that depend on the Request URI scheme, host, or port values need to consider this change. How the placement of the baseURI decorator in a configuration impacts the Request URI value will be shown in more detail in the next section, along with usage examples.

Without a baseURI decorator applied, the Request URI does not get updated and in the case of IG acting as a proxy, IG would end up making a request back to itself resulting in an endless loop.

The UriRouterContext contains routing information associated with a request. A key value is the originalURI, which represents the original target URI for the request, as seen by the IG container. 

In the above example, the originalURI value would most likely be http://ig.internal.example.com:8080/simple but, this will depend on factors such as the container configuration or the Host header value. The originalURI value is read-only and is not changed by the baseURI decorator; it can only be changed by adding a new UriRouterContext set with a new originalURI value.

Note

The examples/details in this article are based on IG 7.0.2 and unless otherwise noted, are applicable to earlier IG releases and later 7.x releases too.

Setting up the Examples

The following details describe how the examples shown in this document were set up and run.

Components:

  • IG 7.0.2 (WAR and standalone)
  • OpenJDK 11
  • Apache Tomcat™ 9
  • Haproxy 2.3
  • Curl 7.64.1 (acting as user-agent)

URIs:

  • https://site.example.com:8443 - load-balancer
  • http://ig.internal.example.com:8080 - IG
  • http://app.internal.example.com:7070 - Application being proxied by IG

Setting up Haproxy for SSL termination is outside the scope of this article, this blog post has a good summary of the process. The following Haproxy configuration was used for these examples, adjust as required:

global    maxconn 256     tune.ssl.default-dh-param 2048 defaults     mode http     option http-server-close     timeout connect 10000ms     timeout client 10000ms     timeout server 10000ms frontend https-in     bind *:8443 ssl crt ca.pem ca-file ca.crt verify optional     http-request add-header X-Forwarded-Scheme https     default_backend servers backend servers     server server1 ig.internal.example.com:8080 maxconn 32

The following admin.json was used for the examples using IG standalone, adjust as required:

{    "connectors": [{         "port": 8080     }] }

The following config.json was used for these examples, adjust as required:

{    "heap": [{             "type": "ScriptableFilter",             "name": "Logger",             "config": {                 "type": "application/x-groovy",                 "source": [                     "logger.info('\\nheaders: {} \\nrequestUri: {} \\noriginalUri: {}', request.headers.asMapOfHeaders(), request.uri, contexts.router.originalUri)",                     "next.handle(context, request)"                 ]             }         },         {             "type": "ForwardedRequestFilter",             "name": "ForwardedRequestFilter",             "config": {                 "scheme": "${request.headers['X-Forwarded-Scheme'][0]}",                 "host": "${split(request.headers['Host'][0], ':')[0]}",                 "port": "${integer(split(request.headers['Host'][0], ':')[1])}"             }         },         {             "type": "StaticResponseHandler",             "name": "Static Page",             "config": {                 "status": 200,                 "reason": "OK",                 "headers": {                     "Content-Type": ["text/html"]                 },                 "entity": "<html><body>Static Page</body></html>"             }         }     ],     "handler": {         "type": "Router",         "name": "Router"     } }The heap in this file includes the following filters and handler:

  • A ScriptableFilter that dumps some key values into the log.
  • A configured ForwardedRequestFilter that rebases the originalURI based on the Host header along with a custom header called X-Forwarded-Scheme (to be provided by the load-balancer).
  • A StaticResponseHandler to return a very simple static response rather than needing to run an additional application, which is not needed for these examples.

baseURI Usage

The following example route uses the Logger defined in the config.json to show the changes to the Request URI after successive applications of the baseURI decorator:

{    "name": "BaseURI Example Route",     "condition": "${matches(request.uri.path,'^/base')}",     "handler": {         "type": "Chain",         "config": {             "filters": [                 "Logger"             ],             "handler": {                 "baseURI": "http://outer.internal.example.com:5555",                 "type": "Chain",                 "name": "Outer Chain",                 "config": {                     "filters": [                         "Logger"                     ],                     "handler": {                         "baseURI": "http://app.internal.example.com:7070",                         "type": "Chain",                         "name": "Inner Chain",                         "config": {                             "filters": [                                 "Logger"                             ],                             "handler": "Static Page"                         }                     }                 }             }         }     } }

Example request:$ curl -k https://site.example.com:8443/base

Example output:[vert.x-eventloop-thread-6] INFO o.f.o.f.ScriptableFilter.Logger @99-base - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://site.example.com:8443/base originalUri: http://site.example.com:8443/base [vert.x-eventloop-thread-6] INFO o.f.o.f.ScriptableFilter.Logger @99-base - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://outer.internal.example.com:5555/base originalUri: http://site.example.com:8443/base [vert.x-eventloop-thread-6] INFO o.f.o.f.ScriptableFilter.Logger @99-base - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://app.internal.example.com:7070/base originalUri: http://site.example.com:8443/base

We can see that the request URI starts out as http://site.example.com:8443/base because there is no top-level baseURI decorator in the route. The Request URI then gets rebased to http://outer.internal.example.com:5555/base after the first "baseURI": "http://outer.internal.example.com:5555" decorator is applied and then finally to http://app.internal.example.com:7070/base after the last "baseURI": "http://app.internal.example.com:7070" decorator is applied.

In most cases, routes will have a single baseURI decorator defined in the top-level, the above example is designed to show how the Request URI value is changed by the baseURI decorator depending on its placement.

Note

As of IG 7.1, there is a useOriginalUri property in SamlFederationHandler to prevent errors that occur when a baseUri decorator applies to the whole route. This change forces the handler to use the original URI instead of the rebased URI when validating RelayState and Assertion Consumer Location URLs. See SamlFederationHandler for further information.

In pre-IG 7.1, it is important you don't apply a baseURI decorator at a higher level (config.json) or at the same level as the SamlFederationHandler as it will alter the Request URI before the validation can be applied, which will result in failed SAML2 interactions. This is due to the way the Request URI value is used within the SamlFederationHandler during RelayState and AssertionConsumerService location validation.

originalURI Usage

Because the originalURI value represents the original request URI that arrived at IG, it is most often used when generating redirects or goto values as part of an authentication flow, that is, the user-agent sends a request to IG without a valid token and is sent to an authentication service to authenticate, after which time the user is returned to IG to continue the flow where they left off. For example:

"loginEndpoint" : "https://auth.example.com/login?goto=${urlEncodeQueryParameterNameOrValue(contexts.router.originalUri)}"

The OAuth2ClientFilter makes use of the originalURI when constructing the various callback/login/logout URIs. The CrossDomainSingleSignOnFilter and SingleSignOnFilter make use of the originalURI when constructing the goto parameter sent in the login redirect to Identity Cloud or AM. See the following links for further information:

The key scenario when the originalURI value may not be completely accurate is when IG is running behind a load-balancer or proxy, often also being used as a SSL offloading point. There are a number of approaches that can be used to make the originalURI value accurate depending on if IG is running within a container or standalone (IG 7 and later).

Scenario: IG standalone running behind a load-balancer

  • No alteration to the originalURI value using the following example route:{    "name": "Default Standalone Original Route",     "condition": "${matches(request.uri.path,'^/standalone-original')}",     "handler": {         "type": "Chain",         "config": {             "filters": [                 "Logger"             ],             "handler": "Static Page"         }     } }

Example request:$ curl -k https://site.example.com:8443/standalone-original

Example output:[vert.x-eventloop-thread-9] INFO o.f.o.f.ScriptableFilter.Logger @99-standalone - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://site.example.com:8443/standalone-original originalUri: http://site.example.com:8443/standalone-original

  • Use the ForwardedRequestFilter to override the originalURI value using the following example route:{    "name": "Default Standalone Original Forwarded Route",     "condition": "${matches(request.uri.path,'^/standalone-original-forwarded')}",     "handler": {         "type": "Chain",         "config": {             "filters": [                 "Logger",                 "ForwardedRequestFilter",                 "Logger"             ],             "handler": "Static Page"         }     } }See ForwardedRequestFilter for further information.

Example request:$ curl -k https://site.example.com:8443/standalone-original-forwarded

Example output:[vert.x-eventloop-thread-2] INFO o.f.o.f.ScriptableFilter.Logger @99-forwarded - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://site.example.com:8443/standalone-original-forwarded originalUri: http://site.example.com:8443/standalone-original-forwarded [vert.x-eventloop-thread-2] INFO o.f.o.f.ScriptableFilter.Logger @99-forwarded - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://site.example.com:8443/standalone-original-forwarded originalUri: https://site.example.com:8443/standalone-original-forwardedHere we can see the before (http://site.example.com:8443/standalone-original-forwarded) and after (https://site.example.com:8443/standalone-original-forwarded) effect of the ForwardedRequestFilter on the originalURI value.

The key difference between the originalURI values across these two route examples are the scheme (HTTP versus HTTPS) because in both cases, IG standalone uses the Host header (host: site.example.com:8443) to get most of the information correct. If, the Host header had not been accurate there would have been more differences. One reason the Host header might not be accurate when it reaches IG is there are multiple levels of load-balancers or firewalls in front of IG.

Note

This ForwardedRequestFilter approach can also be used when IG is run in a container such as Tomcat. The ForwardedRequestFilter is available in IG 7.0.2 and later.

Scenario: IG WAR deployed in Tomcat running behind a load-balancer

  • Use the following example route: {    "name": "WAR Original Route",     "condition": "${matches(request.uri.path,'^/war-original')}",     "handler": {         "type": "Chain",         "config": {             "filters": [                 "Logger"             ],             "handler": "Static Page"         }     } }

Example request:$ curl -k https://site.example.com:8443/war-original

Example output[http-nio-8080-exec-1] INFO o.f.o.f.ScriptableFilter.Logger @99-war-original - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: http://site.example.com:8443/war-original originalUri: http://site.example.com:8443/war-original

  • Update Tomcat server.xml Connector configuration, for example:<Connector port="8080" protocol="HTTP/1.1"           proxyName="site.example.com" proxyPort="8443" scheme="https"            connectionTimeout="20000"            redirectPort="8443" />

Example request after server.xml updates:$ curl -k https://site.example.com:8443/war-original

Example output:[http-nio-8080-exec-1] INFO o.f.o.f.ScriptableFilter.Logger @99-war-original - headers: {accept=[accept: */*], connection=[Connection: close], host=[host: site.example.com:8443], user-agent=[user-agent: curl/7.64.1], x-forwarded-scheme=[x-forwarded-scheme: https]} requestUri: https://site.example.com:8443/war-original originalUri: https://site.example.com:8443/war-original

The key difference the originalURI values are the scheme (originalUri: http://site.example.com:8443/war-original versus originalUri: https://site.example.com:8443/war-original) because in both cases, the container uses the Host header to get most of the information correct. If the Host header had been incorrect, then there would have been more differences.

Summary

The placement of the baseURI decorator is key to deciding how requests are updated before being sent to downstream applications and when they are applied needs to be considered if using the scheme, host or port values to make decisions.

The originalURI value plays an important part in constructing redirects and goto parameters within IG, and depending on how IG is deployed, may need to be updated to better reflect the URI that is used for these redirects or goto parameter values.

See Also

IG (All versions) redirects to HTTP when a reverse proxy or load balancer is doing SSL/TLS offloading

Configuration Reference

Related Training

ForgeRock Identity Gateway Core Concepts (IG-400) 

Related Issue Tracker IDs

OPENIG-5405 (Provide access to the originalUri value when processing SAML2 requests)



Copyright and TrademarksCopyright © 2021 ForgeRock, all rights reserved.
Loading...