About IG

The following sections introduce IG:

IG As an HTTP Gateway

Most organizations have valuable existing services that are not easily integrated into newer architectures. These existing services cannot often be changed. Many client applications cannot communicate as they lack a gateway to bridge the gap. The following image illustrates an example of a missing gateway.

missing-gateway

IG works as an HTTP gateway, based on reverse proxy architecture. IG is deployed on a network, so that it can intercept client requests and server responses. IG can check the identity of HTTP traffic, blocking requests without permission, and letting allowed requests pass. IG can also adapt requests and responses. For example, IG can add headers and change the message payload.

The following image illustrates how a request and response flow between a client and application:

gateway-deployed

Clients interact with protected servers through IG. IG can be configured to add new capabilities to existing services without affecting current clients or servers.

IG provides the following features:

  • 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 supports these capabilities as out of the box configuration options. Once you understand the essential concepts covered in this chapter, try the additional instructions in this guide to use IG to add other features.

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.

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 AdminHttpApplication (admin.json) GatewayHttpApplication (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 Change the Default Location of the Configuration Folders.

IG Configuration Directories and Files
Purpose Default location on Linux, macOS, and UNIX systems Default location on Windows

Administration and gateway configuration, admin.json and config.json. For more information, see AdminHttpApplication (admin.json) and GatewayHttpApplication (config.json)

$HOME/.openig/config

appdata\OpenIG\config

Route configuration files. For more information, see Configure Routers and Routes.

$HOME/.openig/routes

appdata\OpenIG\config\routes

SAML 2.0 configuration files. For more information, see Act As a SAML 2.0 Service Provider.

$HOME/.openig/SAML

appdata\OpenIG\OpenIG\SAML

Script files, for Groovy scripted filters and handlers. For more information, see Extend IG.

$HOME/.openig/scripts/groovy

appdata\OpenIG\scripts\groovy

Temporary storage files.

To change the directory, configure the property temporaryDirectory in admin.json. For more information, see AdminHttpApplication (admin.json).

$HOME/.openig/tmp

appdata\OpenIG\OpenIG\tmp

JSON schema for the topic of a custom audit event. For more information, see Record Custom Audit Events.

To change the directory, configure the property topicsSchemasDirectory in AuditService. For more information, see AuditService.

$HOME/.openig/audit-schemas

appdata\OpenIG\OpenIG\audit-schemas

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://openam.example.com:8088/openam/",
        "version": "7.1"
      }
    }
  ],
  "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://openam.example.com:8088/openam/",
        "version": "7.1"
      }
    }
  ],
  "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://openam.example.com:8088/openam/",
        "version": "7.1"
      }
    }
  ],
  "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://openam.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,
      "reason": "OK",
      "headers": {
        "Content-Type": [ "text/plain" ]
      },
      "entity": "Hello world, from myroute!"
    }
  },
  "condition": "${matches(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://openig.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,
      "reason": "OK",
      "headers": {
        "Content-Type": [ "text/plain" ]
      }
      "entity": "Hello, world from myroute1!"
    }
  },
  "condition": "${matches(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://openig.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://openig.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://openig.example.com:8080/openig/api/system/objects/_router?_api

{
     "swagger": "2.0",
     "info": {
     "version": "IG version",
     "title": "IG"
     },
     "host": "openig.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://openig.example.com:8080/openig/api?_api

{
     "swagger": "2.0",
     "info": {
     "version": "IG version",
     "title": "IG"
     },
     "host": "openig.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.

About 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.

About 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 ForgeRock 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.

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.

  • 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.

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://openam.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://openam.example.com:8088/openam/oauth2/connect/jwk_uri"
      },
      "issuer": "http://openam.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.