Identity Gateway 7.2

About IG

IG as a proxy

Many organizations have existing services that cannot easily be integrated into newer architectures. Similarly, many existing client applications cannot communicate with services. This section describes how IG acts as an intermediary, or proxy, between clients and services.

IG as a reverse proxy

IG as a reverse proxy server is an intermediate connection point between external clients and internal services. IG intercepts client requests and server responses, enforcing policies, and routing and shaping traffic. The following image illustrates IG as a reverse proxy:

IG helps integrate existing services into newer architectures.

IG provides the following features as a reverse proxy:

  • Access management integration

  • Application and API security

  • Credential replay

  • OAuth 2.0 support

  • OpenID Connect 1.0 support

  • Network traffic control

  • Proxy with request and response capture

  • Request and response rewriting

  • SAML 2.0 federation support

  • Single sign-on (SSO)

IG as a forward proxy

In contrast, IG as a forward proxy is an intermediate connection point between an internal client and an external service. IG regulates outbound traffic to the service, and can adapt and enrich requests. The following image illustrates IG as a forward proxy:

IG regulates traffic

IG provides the following features as a forward proxy:

  • Addition of authentication or authorization to the request

  • Addition of tracer IDs to the requests

  • Addition or removal of request headers or scopes

IG as a microgateway

IG is optimized to run as a microgateway in containerized environments. Use IG with business microservices to separate the security concerns of your applications from their business logic. For example, use IG with the ForgeRock Token Validation Microservice to provide access token validation at the edge of your namespace.

For an example, see Protect a microservice with IG as a microgateway. The following image illustrates the request flow in an example deployment:

Example deployment of IG as a microgateway.

The request is processed in the following sequence:

  1. A client requests access to Secured Microservice A, providing a stateful OAuth 2.0 access token as credentials.

  2. Microgateway A intercepts the request, and passes the access token for validation to the Token Validation Microservice, using the /introspect endpoint.

  3. The Token Validation Microservice requests the Authorization Server to validate the token.

  4. The Authorization Server introspects the token, and sends the introspection result to the Token Validation Microservice.

  5. The Token Validation Microservice caches the introspection result, and sends it to Microgateway A, which forwards the result to Secured Microservice A.

  6. Secured Microservice A uses the introspection result to decide how to process the request. In this case, it continues processing the request. Secured Microservice A asks for additional information from Secured Microservice B, providing the validated token as credentials.

  7. Microgateway B intercepts the request, and passes the access token to the Token Validation Microservice for validation, using the /introspect endpoint.

  8. The Token Validation Microservice retrieves the introspection result from the cache, and sends it back to Microgateway B, which forwards the result to Secured Microservice B.

  9. Secured Microservice B uses the introspection result to decide how to process the request. In this case it passes its response to Secured Microservice A, through Microgateway B.

  10. Secured Microservice A passes its response to the client, through Microgateway A.

Processing requests and responses

The following sections describe how IG processes requests and responses:

IG object model

IG processes HTTP requests and responses by passing them through user-defined chains of filters and handlers. The filters and handlers provide access to the request and response at each step in the chain, and make it possible to alter the request or response, and collect contextual information.

The following image illustrates a typical sequence of events when IG processes a request and response through a chain:

chain

When IG processes a request, it first builds an object representation of the request, including parsed query/form parameters, cookies, headers, and the entity. IG initializes a runtime context to provide additional metadata about the request and applied transformations. IG then passes the request representation into the chain.

In the request flow, filters modify the request representation and can enrich the runtime context with computed information. In the ClientHandler, the entity content is serialized, and additional query parameters can be encoded as described in RFC 3986: Query.

In the response flow, filters build a response representation with headers and the entity.

The route configuration in Adding headers and logging results demonstrates the flow through a chain to a protected application.

Configuring IG

The way that IG processes requests and responses is defined by the configuration files admin.json and config.json, and by Route configuration files. For information about the different files used by IG, see Configuration directories and files.

Configuration files are flat JSON files that define objects with the following parts:

  • name: A unique string to identify the object. When you declare inline objects, the name is not required.

  • type: The type name of the object. IG defines many object types for different purposes.

  • config: Additional configuration settings for the object. The content of the configuration object depends on its type. For information about each object type available in the IG configuration, see the Reference.

    If all of the configuration settings for the type are optional, the config field is also optional. The object uses default settings when the config field isn’t configured, or is configured as an empty object ("config": {}), or is configured as null ("config": null).

    Filters, handlers, and other objects whose configuration settings are defined by strings, integers, or booleans, can be defined by expressions that match the expected type.

For information about the objects available, see admin.json, config.json and Route. An IG route typically contains at least the following parts:

  • handler: An object to specify the point where the request enters the route. If the handler type is a Chain, the request is dispatched to a list of filters, and then to another handler.

    Handler objects produce a response for a request, or delegate the request to another handler. Filter objects transform data in the request, response, or context, or perform an action when the request or response passes through the filter.

  • baseURI: A handler decorator to override the scheme, host, and port of the request URI. After a route processes a request, it reroutes the request to the baseURI, which most usually points to the application or service that IG is protecting.

  • heap: A collection of named objects configured in the top level of config.json or in individual routes. Heap objects can be configured once and used multiple times in the configuration.

    A heap object in a route can be used in that route. A heap object in config.json can be used across the whole configuration, unless it is overridden in a route.

  • condition: An object to define a condition that a request must meet. A route can handle a request if condition is not defined, or if the condition resolves to true.

Routes inherit settings from their parent configurations. This means that you can configure objects in the config.json heap, for example, and then reference those objects by name in any other IG configuration.

Configuration directories and files

By default, IG configuration files are located under $HOME/.openig on Linux, macOS, and UNIX systems, and %appdata%\OpenIG on Windows systems. For information about how to change the default locations, see Configure default location.

Using comments in IG configuration files

The JSON format does not specify a notation for comments. If IG does not recognize a JSON field name, it ignores the field. As a result, it is possible to use comments in configuration files.

The following conventions are available for commenting:

  • A comment field to add text comments. The following example includes a text comment.

    {
      "name": "capture",
      "type": "CaptureDecorator",
      "comment": "Write request and response information to the logs",
      "config": {
        "captureEntity": true
      }
    }
  • An underscore (_) to comment a field temporarily. The following example comments out "captureEntity": true, and as a result it uses the default setting ("captureEntity": false).

    {
      "name": "capture",
      "type": "CaptureDecorator",
      "config": {
        "_captureEntity": true
      }
    }

Development mode and production mode

IG operates in the following modes:

  • Development mode (mutable mode)

    In development mode, by default all endpoints are open and accessible.

    You can create, edit, and deploy routes through IG Studio, and manage routes through Common REST, without authentication or authorization.

    Use development mode to evaluate or demo IG, or to develop configurations on a single instance. This mode is not suitable for production.

    For information about how to switch to development mode, see Switching from production mode to development mode.

    For information about restricting access to Studio in development mode, see Restricting access to Studio.

  • Production mode (immutable mode)

    In production mode, the /routes endpoint is not exposed or accessible.

    Studio is effectively disabled, and you cannot manage, list, or even read routes through Common REST.

    By default, other endpoints, such as /share and api/info are exposed to the loopback address only. To change the default protection for specific endpoints, configure an ApiProtectionFilter in admin.json and add it to the IG configuration.

    For information about how to switch to production mode, see Switching from development mode to production mode.

After installation, IG is by default in production mode.

Decorators

Decorators are heap objects to extend what another object can do. IG defines baseURI, capture, and timer decorators that you can use without explicitly configuring them. For more information about the types of decorators provided by IG, see Decorators.

Decorating objects, the route handler, and the heap

Use decorations that are compatible with the object type. For example, timer records the time to process filters and handlers, but does not record information for other object types. Similarly, baseURI overrides the scheme, host, and ports, but has no other effect.

In a route, you can decorate individual objects, the route handler, and the heap. IG applies decorations in this order:

  1. Decorations declared on individual objects. Local decorations that are part of an object’s declaration are inherited wherever the object is used.

  2. globalDecorations declared in parent routes, then in child routes, and then in the current route.

  3. Decorations declared on the route handler.

Decorating individual objects in a route

To decorate individual objects, add the decorator’s name value as a top-level field of the object, next to type and config.

In this example, the decorator captures all requests going into the SingleSignOnFilter, and all responses coming out of the SingleSignOnFilter:

{
  "heap": [
    {
      "name": "SystemAndEnvSecretStore-1",
      "type": "SystemAndEnvSecretStore"
    },
    {
      "name": "AmService-1",
      "type": "AmService",
      "config": {
        "agent": {
          "username": "ig_agent",
          "passwordSecretId": "agent.secret.id"
        },
        "secretsProvider": "SystemAndEnvSecretStore-1",
        "url": "http://am.example.com:8088/openam/",
        "version": "7.2"
      }
    }
  ],
  "handler": {
    "type": "Chain",
    "config": {
      "filters": [
        {
          "capture": "all",
          "type": "SingleSignOnFilter",
          "config": {
            "amService": "AmService-1"
          }
        }
      ],
      "handler": "ReverseProxyHandler"
    }
  }
}

Decorating the route handler

To decorate the handler for a route, add the decorator as a top-level field of the route.

In this example, the decorator captures all requests and responses that traverse the route:

{
  "heap": [
    {
      "name": "SystemAndEnvSecretStore-1",
      "type": "SystemAndEnvSecretStore"
    },
    {
      "name": "AmService-1",
      "type": "AmService",
      "config": {
        "agent" : {
          "username" : "ig_agent",
          "passwordSecretId" : "agent.secret.id"
        },
        "secretsProvider": "SystemAndEnvSecretStore-1",
        "url": "http://am.example.com:8088/openam/",
        "version": "7.2"
      }
    }
  ],
  "capture": "all",
  "handler": {
    "type": "Chain",
    "config": {
      "filters": [
        {
          "type": "SingleSignOnFilter",
          "config": {
            "amService": "AmService-1"
          }
        }
      ],
      "handler": "ReverseProxyHandler"
    }
  }
}

Decorating the route heap

To decorate all compatible objects in a route, configure globalDecorators as a top-level field of the route. The globalDecorators field takes a map of the decorations to apply.

To decorate all compatible objects declared in config.json or admin.json, configure globalDecorators as a top-level field in config.json or admin.json.

In the following example, the route has capture and timer decorations. The capture decoration applies to AmService, Chain, SingleSignOnFilter, and ReverseProxyHandler. The timer decoration doesn’t apply to AmService because it is not a filter or handler, but does apply to Chain, SingleSignOnFilter, and ReverseProxyHandler:

{
  "globalDecorators":
  {
    "capture": "all",
    "timer": true
  },
  "heap": [
    {
      "name": "SystemAndEnvSecretStore-1",
      "type": "SystemAndEnvSecretStore"
    },
    {
      "name": "AmService-1",
      "type": "AmService",
      "config": {
        "agent": {
          "username": "ig_agent",
          "passwordSecretId": "agent.secret.id"
        },
        "secretsProvider": "SystemAndEnvSecretStore-1",
        "url": "http://am.example.com:8088/openam/",
        "version": "7.2"
      }
    }
  ],
  "handler": {
    "type": "Chain",
    "config": {
      "filters": [
        {
          "type": "SingleSignOnFilter",
          "config": {
            "amService": "AmService-1"
          }
        }
      ],
      "handler": "ReverseProxyHandler"
    }
  }
}

Decorating a named object differently in different parts of the configuration

When a filter or handler is configured in config.json or in the heap, it can be used many times in the configuration. To decorate each use of the filter or handler individually, use a Delegate. For more information, see Delegate

In the following example, an AmService heap object configures an amHandler to delegate tasks to ForgeRockClientHandler, and capture all requests and responses passing through the handler.

{
  "type": "AmService",
  "config": {
    "agent" : {
      "username" : "ig_agent",
      "passwordSecretId" : "agent.secret.id"
      },
    "secretsProvider": "SystemAndEnvSecretStore-1",
    "amHandler": {
      "type": "Delegate",
      "capture": "all",
      "config": {
        "delegate": "ForgeRockClientHandler"
      }
    },
    "url": "http://am.example.com:8088/openam"
  }
}

You can use the same ForgeRockClientHandler in another part of the configuration, in a different route for example, without adding a capture decorator. Requests and responses that pass through that use of the handler are not captured.

Decorating IG’s interactions with AM

To log interactions between IG and AM, delegate message handling to a ForgeRockClientHandler, and capture the requests and responses passing through the handler. When the ForgeRockClientHandler communicates with an application, it sends ForgeRock Common Audit transaction IDs.

In the following example, the accessTokenResolver delegates message handling to a decorated ForgeRockClientHandler:

"accessTokenResolver": {
  "name": "token-resolver-1",
  "type": "TokenIntrospectionAccessTokenResolver",
  "config": {
    "amService": "AmService-1",
    "providerHandler": {
      "capture": "all",
      "type": "Delegate",
      "config": {
        "delegate": "ForgeRockClientHandler"
      }
    }
  }
}

To try the example, replace the accessTokenResolver in the IG route of Validate access tokens through the introspection endpoint. Test the setup as described for the example, and note that the route’s log file contains an HTTP call to the introspection endpoint.

Using multiple decorators for the same object

Decorations can apply more than once. For example, if you set a decoration on a route and another decoration on an object defined within the route, IG applies the decoration twice. In the following route, the request is captured twice:

{
 "handler": {
   "type": "ReverseProxyHandler",
   "capture": "request"
 },
 "capture": "all"
}

When an object has multiple decorations, the decorations are applied in the order they appear in the JSON.

In the following route, the handler is decorated with a baseURI first, and a capture second:

{
  "name": "myroute",
  "baseURI": "http://app.example.com:8081",
  "capture": "all",
  "handler": {
    "type": "StaticResponseHandler",
    "config": {
      "status": 200,
      "headers": {
        "Content-Type": [ "text/plain; charset=UTF-8" ]
      },
      "entity": "Hello world, from myroute!"
    }
  },
  "condition": "${find(request.uri.path, '^/myroute1')}"
}

The decoration can be represented as capture[ baseUri[ handler ] ]. When a request is processed, it is captured, and then rebased, and then processed by the handler: The log for this route shows that the capture occurs before the rebase:

2018-09-10T13:23:18,990Z | INFO  | http-nio-8080-exec-1 | o.f.o.d.c.C.c.top-level-handler | @myroute |

--- (request) id:f792d2ad-4409-4907-bc46-28e1c3c19ac3-7 --->

GET http://ig.example.com:8080/myroute HTTP/1.1
...

Conversely, in the following route, the handler is decorated with a capture first, and a baseURI second:

{
  "name": "myroute",
  "capture": "all",
  "baseURI": "http://app.example.com:8081",
  "handler": {
    "type": "StaticResponseHandler",
    "config": {
      "status": 200,
      "headers": {
        "Content-Type": [ "text/plain; charset=UTF-8" ]
      }
      "entity": "Hello, world from myroute1!"
    }
  },
  "condition": "${find(request.uri.path, '^/myroute')}"
}

The decoration can be represented as baseUri[ capture[ handler ] ]. When a request is processed, it is rebased, and then captured, and then processed by the handler. The log for this route shows that the rebase occurs before the capture:

2018-09-10T13:07:07,524Z | INFO  | http-nio-8080-exec-1 | o.f.o.d.c.C.c.top-level-handler | @myroute |

--- (request) id:3c26ab12-3cc0-403e-bec6-43bf5621f657-7 --->

GET http://app.example.com:8081/myroute HTTP/1.1
...

Guidelines for naming decorators

To prevent unwanted behavior, consider the following points when you name decorators:

  • Avoid decorators named comment or comments, and avoid reserved field names. Instead of using alphanumeric field names, consider using dots in your decorator names, such as my.decorator.

  • For heap objects, avoid the reserved names config, name, and type.

  • For routes, avoid the reserved names auditService, baseURI, condition, globalDecorators, heap, handler, name, secrets, and session.

  • In config.json, avoid the reserved name temporaryStorage.

Configuration parameters declared as property variables

Configuration parameters, such as host names, port numbers, and directories, can be declared as property variables in the IG configuration or in an external JSON file. The variables can then be used in expressions in routes and in config.json to set the value of configuration parameters.

Properties can be inherited across the router, so a property defined in config.json can be used in any of the routes in the configuration.

Storing the configuration centrally and using variables for parameters that can be different for each installation makes it easier to deploy IG in different environments without changing a single line in your route configuration.

For more information, see Route properties.

Changing the configuration and restarting IG

You can change routes or change a property that is read at runtime or that relies on a runtime expression without needing to restart IG to take the change into account.

Stop and restart IG only when you make the following changes:

  • Change the configuration of any route, when the scanInterval of Router is disabled (see Router).

  • Add or change an external object used by the route, such as an environment variable, system property, external URL, or keystore.

  • Add or update config.json or admin.json.

  • When IG is running in web container mode, and the container configuration is changed.

Understanding IG APIs with API descriptors

Common REST endpoints in IG serve API descriptors at runtime. When you retrieve an API descriptor for an endpoint, a JSON that describes the API for that endpoint is returned.

To help you discover and understand APIs, you can use the API descriptor with a tool such as Swagger UI to generate a web page that helps you to view and test the different endpoints.

When you start IG, or add or edit routes, registered endpoint locations for the routes hosted by the main router are written in $HOME/.openig/logs/route-system.log, where $HOME/.openig is the instance directory. Endpoint locations for subroutes are written to other log files. To retrieve the API descriptor for a specific endpoint, append one of the following query string parameters to the endpoint:

  • _api, to represent the API accessible over HTTP. This OpenAPI descriptor can be used with endpoints that are complete or partial URLs.

    The returned JSON respects the OpenAPI specification and can be consumed by Swagger tools, such as Swagger UI.

  • _crestapi, to provide a compact representation that is independent of the transport protocol. This ForgeRock® Common REST (Common REST) API descriptor cannot be used with partial URLs.

    The returned JSON respects a ForgeRock proprietary specification dedicated to describe Common REST endpoints.

For more information about Common REST API descriptors, see Common REST API Documentation.

Retrieving API descriptors for a router

With IG running as described in the Getting started, run the following query to generate a JSON that describes the router operations supported by the endpoint:

$ curl http://ig.example.com:8080/openig/api/system/objects/_router/routes?_api

{
     "swagger": "2.0",
     "info": {
     "version": "IG version",
     "title": "IG"
     },
     "host": "0:0:0:0:0:0:0:1",
     "basePath": "/openig/api/system/objects/_router/routes",
     "tags": [{
     "name": "Routes Endpoint"
     }],
     . . .

Alternatively, generate a Common REST API descriptor by using the ?_crestapi query string.

Retrieving API descriptors for the UMA service

With the UMA tutorial running as described in Support UMA resource servers, run the following query to generate a JSON that describes the UMA share API:

$ curl http://ig.example.com:8080/openig/api/system/objects/_router/routes/00-uma/objects/umaservice/share?_api

{
     "swagger": "2.0",
     "info": {
     "version": "IG version",
     "title": "IG"
     },
     "host": "0:0:0:0:0:0:0:1",
     "basePath": "/openig/api/system/objects/_router/routes/00-uma/objects/umaservice/share",
     "tags": [{
     "name": "Manage UMA Share objects"
     }],
     . . .

Alternatively, generate a Common REST API descriptor by using the ?_crestapi query string.

Retrieving API descriptors for the main router

Run a query to generate a JSON that describes the API for the main router and its subsequent endpoints. For example:

$ curl http://ig.example.com:8080/openig/api/system/objects/_router?_api

{
     "swagger": "2.0",
     "info": {
     "version": "IG version",
     "title": "IG"
     },
     "host": "ig.example.com:8080",
     "basePath": "/openig/api/system/objects/_router",
     "tags": [{
     "name": "Monitoring endpoint"
     }, {
     "name": "Manage UMA Share objects"
     }, {
     "name": "Routes Endpoint"
     }],
     . . .

Because the above URL is a partial URL, you cannot use the ?_crestapi query string to generate a Common REST API descriptor.

Retrieving API descriptors for an IG instance

Run a query to generate a JSON that describes the APIs provided by the IG instance that is responding to a request. For example:

$ curl http://ig.example.com:8080/openig/api?_api

{
     "swagger": "2.0",
     "info": {
     "version": "IG version",
     "title": "IG"
     },
     "host": "ig.example.com:8080",
     "basePath": "/openig/api",
     "tags": [{
     "name": "Internal Storage for UI Models"
     }, {
     "name": "Monitoring endpoint"
     }, {
     "name": "Manage UMA Share objects"
     }, {
     "name": "Routes Endpoint"
     }, {
     "name": "Server Info"
     }],
     . . .

If routes are added after the request is performed, they are not included in the returned JSON.

Because the above URL is a partial URL, you cannot use the ?_crestapi query string to generate a Common REST API descriptor.

Sessions

IG uses sessions to group requests from a user agent or other source, and collect information from the requests. When multiple requests are made in the same session, the requests can share the session information. Because session sharing is not thread-safe, it is not suitable for concurrent exchanges.

The following table compares stateful and stateless sessions:

Feature Stateful sessions Stateless sessions

Cookie size.

Unlimited.

Max 4 KBytes.

Default name of the session cookie.

IG_SESSIONID.

openig-jwt-session.

Object types that can be stored in the session.

Only Java serializable objects, when sessions are replicated.

Any object, when sessions are not replicated.

JSON-compatible types, such as strings, numbers, booleans, null, structures such as arrays, and list and maps containing only JSON-compatible types.

Session sharing between instances of IG, for load balancing and failover.

Possible when sessions are replicated on multiple IG instances.

Possible when sessions are not replicated, if session stickiness is configured.

Possible because the session content is a cookie on the user agent, that can be copied to multiple instances of IG.

Risk of data inconsistency when simultaneous requests modify the content of a session.

Low because the session content is stored on IG and shared by all exchanges.

Processing is not thread-safe.

Higher because the session content is reconstructed for each request.

Concurrent exchanges don’t see the same content.

Stateful sessions

When a JwtSession is not configured for a request, stateful sessions are created automatically. Session information is stored in the IG cookie, called IG_SESSIONID by default. When the user agent sends a request with the cookie, the request can access the session information on IG.

When a JwtSession object is configured in the route that processes a request, or in its ascending configuration (a parent route or config.json), the session is always stateless and can’t be stateful.

When a request enters a route without a JwtSession object in the route or its ascending configuration, a stateful session is created lazily. The session lasts as follows:

  • For IG in standalone mode, the duration defined the session property in admin.json, with a default of 30 minutes. For more information, see the session property of AdminHttpApplication (admin.json).

  • For IG in web container mode, until the session reaches the timeout configured by the web container.

Even if the session is empty, the session remains usable until the timeout.

When IG is not configured for session replication, any object type can be stored in a stateful session.

Because session content is stored on IG, and shared by all exchanges, when IG processes simultaneous requests in a stateful session there is low risk that the data becomes inconsistent. However, sessions are not thread-safe; different requests can simultaneously read and modify a shared session.

Session information is available in SessionContext to downstream handlers and filters. For more info see SessionContext.

Considerations for clustering IG

When a stateful session is replicated on the multiple IG instances, consider the following points:

  • The session can store only object types that can be serialized.

  • The network latency of session replication introduces a delay that can cause the session information of two IG instances to desynchronize.

  • Because the session is replicated on the clustered IG instances, it can be shared between the instances, without configuring session stickiness.

  • When sessions are not shared, configure session stickiness to ensure that load balancers serve requests to the same IG instance. For more information, see Prepare for load balancing and failover.

Configuring stateful sessions

To configure stateful sessions in standalone mode, update the session property of admin.json.

To configure stateful sessions in web container mode, configure the container WEB-INF/web.xml file when you unpack the IG .war file.

For example, add the following example lines to Tomcat’s WEB-INF/web.xml to configure a stateful session:

<session-config>
  <tracking-mode>COOKIE</tracking-mode>
  <session-timeout>15</session-timeout>
  <cookie-config>
    <name>non-default-anonymized-name</name>
    <http-only>true</http-only>
    <secure>true</http-only>
  </cookie-config>
</session-config>

For more information, see the configuration documentation for your container.

Stateless sessions

Stateless sessions are provided when a JwtSession object is configured in config.json or in a route. For more information about configuring stateless sessions, see JwtSession.

IG serializes stateless session information as JSON, stores it in a JWT that can be encrypted and then signed, and places the JWT in a cookie. The cookie contains all of the information about the session, including the session attributes as JSON, and a marker for the session timeout.

Only JSON-compatible object types can be stored in stateless sessions. These object types include strings, numbers, booleans, null, structures such as arrays, and list and maps containing only JSON-compatible types.

Stateless sessions are managed as follows:

  • When a request enters a route with a JwtSession object in the route or its ascending configuration, IG creates the SessionContext, verifies the cookie signature, decrypts the content of the cookie, and checks that the current date is before the session timeout.

  • When the request passes through the filters and handlers in the route, the request can read and modify the session content.

  • When the request returns to the the point where the session was created, for example, at the entrance to a route or at config.json, IG updates the cookie as follows:

    • If the session content has changed, IG serializes the session, creates a new cookie with the new content, encrypts and then signs the new cookie, assigns it an appropriate expiration time, and returns the cookie in the response.

    • If the session is empty, IG deletes the session, creates a new cookie with an expiration time that has already passed, and returns the cookie in the response.

    • If the session content has not changed, IG does nothing.

Because the session content is stored in a cookie on the user agent, stateless sessions can be shared easily between IG instances. The cookie is automatically carried over in requests, and any IG instance can unpack and use the session content.

When IG processes simultaneous requests in stateless sessions, there is a high risk that the data becomes inconsistent. This is because the session content is reconstructed for each exchange, and concurrent exchanges don’t see the same content.

IG does not share sessions across requests. Instead, each request has its own session objects that it modifies as necessary, writing its own session to the session cookie regardless of what other requests do.

Session information is available in SessionContext to downstream handlers and filters. For more information, see SessionContext.

Secrets

IG uses the Commons Secrets Service to manage secrets, such as passwords and cryptographic keys.

Repositories of secrets are managed through secret stores, provided to the configuration by the SecretsProvider object or secrets object. For more information about these objects and the types of secret stores provided in IG, see SecretsProvider and Secrets object and secret stores.

Secret names and types

The following terms are used to describe secrets:

  • Secret ID: A label to indicate the purpose of a secret. A secret ID is generally associated with one or more aliases of a key in a keystore or HSM.

  • Stable ID: A label to identify a secret. The stable ID corresponds to the following values in each type of secret store:

    • Base64EncodedSecretStore: The value of secret-id in the "secret-id": "string" pair.

    • FileSystemSecretStore: The filename of a file in the specified directory, without the prefix/suffix defined in the store configuration.

    • HsmSecretStore: The value of an alias in a secret-id/aliases mapping.

    • JwkSetSecretStore: The value of the kid of a JWK stored in a JwkSetSecretStore.

    • KeyStoreSecretStore: The value of an alias in a secret-id/aliases mapping.

    • SystemAndEnvSecretStore: The name of a system property or environment. variable

  • Valid secret: A secret whose purpose matches the secret ID and any purpose contsraints. Contsraints can include requirements for the following:

    • Secret type, such as signing key or encryption key

    • Cryptographic algorithm, such as Diffie-Hellman and RSA

    • Signature algorithm, such as ES256 and ES384

    Constraints are defined when the secret is generated, and cannot be added after.

  • Named secret: A valid secret that a secret store can find by using a secret ID and stable ID.

  • Active secret: One of the valid secrets that is considered eligible at the time of use. The way that the active secret is chosen is determined by the type of secret store. For more information, see Secrets object and secret stores,

Using keys and certificates with IG in standalone mode

The examples in this doc set use self-signed certificates, but your deployment is likely to use certificates issued by a certificate authority (CA certificates).

The way to obtain CA certificates depends on the certificate authority that you are using, and is not described in this document. As an example, see Let’s Encrypt.

When IG is in web container mode, the way to integrate CA certificates depends on the web container type; see your web container documentation for more information. When IG is in standalone mode, integrate CA certificates by using secret stores:

Note the following points about using secrets:

  • When IG in standalone mode starts up, it listens for HTTPS connections, using the ServerTlsOptions configuration in admin.json. The keys and certificates are fetched only once, at startup.

  • Keys and certificates must be present at startup.

  • If keys or certificates change, you must to restart IG.

For information about secret stores provided in IG, see Secrets object and secret stores.

Validating the signature of signed tokens

IG validates the signature of signed tokens as follows:

  • Named secret resolution:

    • If the JWT contains a kid, IG queries the secret stores declared in secretsProvider or secrets to find a named secret, identified by a secret ID and stable ID.

    • If a named secret is found, IG then uses the named secret to try to validate the signature. If the named secret can’t validate the signature, the token is considered as invalid.

    • If a named secret isn’t found, IG tries valid secret resolution.

  • Valid secret resolution:

    • IG uses the value of verificationSecretId as the secret ID, and queries the declared secret stores to find all secrets that match the provided secret ID.

    • All matching secrets are returned as valid secrets, in the order that the secret stores are declared, and for KeyStoreSecretStore and HsmSecretStore, in the order defined by the mappings.

    • IG tries to verify the signature with each valid secret, starting with the first valid secret, and stopping when it succeeds.

    • If no valid secrets are returned, or if none of the valid secrets can verify the signature, the token is considered as invalid.

Validating the signature of signed tokens by using a KeyStoreSecretStore

In the following example, a StatelessAccessTokenResolver validates a signed access token by using a KeyStoreSecretStore:

"accessTokenResolver": {
  "type": "StatelessAccessTokenResolver",
  "config": {
    "secretsProvider": {
      "type": "KeyStoreSecretStore",
      "config": {
        "file": "IG_keystore.p12",
        "storeType": "PKCS12",
        "storePassword": "keystore.secret.id",
        "keyEntryPassword": "keystore.secret.id",
        "mappings": [{
          "secretId": "verification.secret.id",
          "aliases": [ "verification.key.1", "verification.key.2" ]
        }]
      },
      "issuer": "http://am.example.com:8088/openam/oauth2",
      "verificationSecretId": "verification.secret.id"
    }
  }
}

The JWT signature is validated as follows:

  • If the JWT contains a kid with a mapped value, for example verification.key.1:

    • The secrets provider queries the KeyStoreSecretStore for a named secret with the secret ID verification.secret.id and the stable ID verification.key.1.

    • Because the KeyStoreSecretStore contains that mapping, the KeyStoreSecretStore returns a named secret.

    • The StatelessAccessTokenResolver tries to validate the JWT signature with the named secret. If it fails, the token is considered as invalid.

  • If the JWT contains a kid with an unmapped value, for example, verification.key.3:

    • The secrets provider queries the KeyStoreSecretStore for a named secret with the secret ID verification.secret.id and the stable ID verification.key.3.

    • Because the KeyStoreSecretStore doesn’t contain that mapping, named secret resolution fails. IG tries valid secret resolution in the same way as when the JWT doesn’t contain a kid.

  • If the JWT doesn’t contain a kid:

    • The secrets provider queries the KeyStoreSecretStore for all valid secrets, whose alias is mapped to the secret ID verification.secret.id. There are two valid secrets, with aliases verification.key.1 and verification.key.2.

    • The StatelessAccessTokenResolver first tries to verify the signature with verification.key.1. If that fails, it tries verification.key.2.

    • If neither of the valid secrets can verify the signature, the token is considered as invalid.

Validating the signature of signed tokens with a JwkSetSecretStore

In the following example, a StatelessAccessTokenResolver validates a signed access token by using a JwkSetSecretStore:

"accessTokenResolver": {
  "type": "StatelessAccessTokenResolver",
  "config": {
    "secretsProvider": {
      "type": "JwkSetSecretStore",
      "config": {
        "jwkUrl": "http://am.example.com:8088/openam/oauth2/connect/jwk_uri"
      },
      "issuer": "http://am.example.com:8088/openam/oauth2",
      "verificationSecretId": "verification.secret.id"
    }
  }
}

The JWT signature is validated as follows:

  • If the JWT contains a kid with a matching secret in the JWK set:

    • The secrets provider queries the JwkSetSecretStore for a named secret.

    • The JwkSetSecretStore returns the matching secret, identified by a stable ID.

    • The StatelessAccessTokenResolver tries to validate the signature with that named secret. If it fails, the token is considered as invalid.

    In the route, note that the property verificationSecretId must be configured but is not used in named secret resolution.

  • If the JWT contains a kid without a matching secret in the JWK set:

    • The secrets provider queries the JwkSetSecretStore for a named secret.

    • Because the referenced JWK set doesn’t contain a matching secret, named secret resolution fails. IG tries valid secret resolution in the same way as when the JWT doesn’t contain a kid.

  • If the JWT doesn’t contain a kid:

    • The secrets provider queries the JwkSetSecretStore for list of valid secrets, whose secret ID is verification.secret.id.

    • The JwkSetSecretStore returns all secrets in the JWK set whose purpose is signature verification. For example, signature verification keys can have the following JWK parameters:

      {
        "use": "sig"
      }
      {
        "key_opts": [ "verify" ]
      }

      Secrets are returned in the order that they are listed in the JWK set.

    • The StatelessAccessTokenResolver tries to validate the signature with each secret sequentially, starting with the first, and stopping when it succeeds.

    • If none of the valid secrets can verify the signature, the token is considered as invalid.

Using multiple secret stores in a configuration

When multiple secrets stores are provided in a configuration, the secrets stores are queried in the following order:

  • Locally in the route, starting with the first secret store in the list, up to the last.

  • In ascending parent routes, starting with the first secret store in each list, up to the last.

  • In config.json, starting with the first secret store in the list, up to the last.

  • If a secrets store is not configured in config.json, the secret is queried in a default SystemAndEnvSecretStore, and a base64-encoded value is expected.

  • If a secret is not resolved, an error is produced.

Secrets stores defined in admin.json can be accessed only by heap objects in admin.json.

Algorithms for elliptic curve digital signatures

When the Elliptic Curve Digital Signature Algorithm (ECDSA) is used for signing, and both of the following conditions are met, JWTs are signed with a deterministic ECDSA:

  • Bouncy Castle is installed.

  • The system property org.forgerock.secrets.preferDeterministicEcdsa is true, which is its default value.

Otherwise, when ECDSA is used for signing, JWTs are signed with a non-deterministic ECDSA.

A non-deterministic ECDSA signature can be verified by the equivalent deterministic algorithm.

For information about deterministic ECDSA, see RFC 6979. For information about Bouncy Castle, see The Legion of the Bouncy Castle.