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.
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:
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:
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 theconfig
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 thebaseURI
, which most usually points to the application or service that IG is protecting. -
heap
: A collection of named objects configured in the top level ofconfig.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 ifcondition
is not defined, or if the condition resolves totrue
.
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.
Purpose | Default location on Linux, macOS, and UNIX systems | Default location on Windows |
---|---|---|
Administration and gateway configuration, |
|
|
Route configuration files. For more information, see Configure Routers and Routes. |
|
|
SAML 2.0 configuration files. For more information, see Act As a SAML 2.0 Service Provider. |
|
|
Script files, for Groovy scripted filters and handlers. For more information, see Extend IG. |
|
|
Temporary storage files. To change the directory, configure the property |
|
|
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 |
|
|
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
andapi/info
are exposed to the loopback address only. To change the default protection for specific endpoints, configure an ApiProtectionFilter inadmin.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:
-
Decorations declared on individual objects. Local decorations that are part of an object’s declaration are inherited wherever the object is used.
-
globalDecorations declared in parent routes, then in child routes, and then in the current route.
-
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": "${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://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": "${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
orcomments
, and avoid reserved field names. Instead of using alphanumeric field names, consider using dots in your decorator names, such asmy.decorator
. -
For heap objects, avoid the reserved names
config
,name
, andtype
. -
For routes, avoid the reserved names
auditService
,baseURI
,condition
,globalDecorators
,heap
,handler
,name
,secrets
, andsession
. -
In
config.json
, avoid the reserved nametemporaryStorage
.
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 isdisabled
(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
oradmin.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.
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.
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.
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.
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. |
|
|
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 inadmin.json
, with a default of 30 minutes. For more information, see thesession
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 asecret-id
/aliases
mapping. -
JwkSetSecretStore: The value of the
kid
of a JWK stored in a JwkSetSecretStore. -
KeyStoreSecretStore: The value of an
alias
in asecret-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 insecretsProvider
orsecrets
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.
-
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 exampleverification.key.1
:-
The secrets provider queries the KeyStoreSecretStore for a named secret with the secret ID
verification.secret.id
and the stable IDverification.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 IDverification.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 aliasesverification.key.1
andverification.key.2
. -
The StatelessAccessTokenResolver first tries to verify the signature with
verification.key.1
. If that fails, it triesverification.key.2
. -
If neither of the valid secrets can verify the signature, the token is considered as invalid.
-
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
istrue
, 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.