PingGateway

Configuring PingGateway for FAPI

To support FAPI, PingGateway plays the role of reverse proxy for the Advanced Identity Cloud authorization server and the role of resource server for protected applications.

PingGateway uses the accounts and routes described here to connect to and protect Advanced Identity Cloud, lets FAPI clients register dynamically, and protects resources for sensitive applications.

Advanced Identity Cloud configuration

Before you begin

  1. Sign on to the Advanced Identity Cloud admin UI as an administrator.

  2. Switch to the realm you use for FAPI.

    This tutorial uses the alpha realm. Adapt the realm name to your deployment.

OAuth 2.0 client accounts

Create as many OAuth 2.0 client accounts as needed for the resource server roles PingGateway plays.

In this tutorial, PingGateway acts as a resource server only as an example, so use the account you created when configuring access management.

PingGateway configuration

The PingGateway routes for FAPI depend on definitions in config.json.

Adapt these settings as necessary for your deployment. In particular, this tutorial uses a test trusted directory service. In production deployments, use your FAPI ecosystem’s official trusted directory service, such as the Open Banking Directory, for example.

Base configuration

  1. Prepare keys for PingGateway server-side TLS and mutual TLS.

    For server-side TLS, HTTPS clients must trust the PingGateway certificate. You can use a CA or generate your own keys as described in Configure PingGateway for TLS (server-side).

    For mutual TLS, PingGateway must trust the test trusted directory’s CA certificate. The test trusted directory uses its CA certificate to sign API client TLS certificates.

    The following example generates a TLS key pair in a keystore named secrets/keystore.p12, exports the PingGateway self-signed certificate as secrets/gateway.pem, and imports the test trusted directory’s CA certificate from a file named secrets/test-trusted-dir-ca.pem:

    touch secrets/keystore.pin
    chmod 600 secrets/keystore.pin
    echo -n password > secrets/keystore.pin
    
    keytool -keystore secrets/keystore.p12 -storetype PKCS12 \
            -genkeypair -alias ssl-key-pair -dname "CN=Gateway TLS keys" \
            -keyalg RSA -keysize 2048 -validity 3650 -ext "san=dns:gateway.example.com" \
            -storepass:file secrets/keystore.pin \
            -keypass:file secrets/keystore.pin
    
    keytool -keystore secrets/keystore.p12 -storetype PKCS12 \
            -exportcert -alias ssl-key-pair -rfc -file secrets/gateway.pem -storepass:file secrets/keystore.pin
    
    keytool -keystore secrets/keystore.p12 -storetype PKCS12 \
            -importcert -trustcacerts -alias ca -rfc \
            -file secrets/test-trusted-dir-ca.pem -storepass:file secrets/keystore.pin -noprompt

    Adjust the hostname in -ext "san=dns:gateway.example.com" to match your deployment.

  2. Add a admin.json file such as the following to use the TLS keys, adapting the port numbers and secrets to match the deployment:

    FAPI admin.json

    (Source: admin-fapi.json)

    {
      "mode": "DEVELOPMENT",
      "heap": [
        {
          "name": "TlsConf",
          "type": "ServerTlsOptions",
          "config": {
            "clientAuth": "REQUEST",
            "keyManager": {
              "type": "SecretsKeyManager",
              "config": {
                "signingSecretId": "key.manager.secret.id",
                "secretsProvider": "KeyStoreSecretStore"
              }
            },
            "trustManager": {
              "type": "SecretsTrustManager",
              "config": {
                "verificationSecretId": "trust.manager.secret.id",
                "secretsProvider": "KeyStoreSecretStore"
              }
            }
          }
        },
        {
          "name": "KeyStoreSecretStore",
          "type": "KeyStoreSecretStore",
          "config": {
            "file": "&{ig.instance.dir}/../secrets/keystore.p12",
            "storePasswordSecretId": "keystore.pin",
            "secretsProvider": {
              "type": "FileSystemSecretStore",
              "config": {
                "directory": "&{ig.instance.dir}/../secrets/",
                "format": "PLAIN"
              }
            },
            "mappings": [
              {
                "secretId": "key.manager.secret.id",
                "aliases": [
                  "ssl-key-pair"
                ]
              },
              {
                "secretId": "trust.manager.secret.id",
                "aliases": [
                  "ca"
                ]
              }
            ]
          }
        }
      ],
      "connectors": [
        {
          "port": 8080,
          "maxTotalHeadersSize": 24576,
          "vertx": {
            "maxInitialLineLength": 8192
          }
        },
        {
          "port": 8443,
          "maxTotalHeadersSize": 24576,
          "tls": "TlsConf",
          "vertx": {
            "maxInitialLineLength": 8192
          }
        }
      ],
      "adminConnector": {
        "port": 9443,
        "host": "localhost",
        "tls": "TlsConf"
      }
    }
  3. Add a config.json file with these settings, adapting the Advanced Identity Cloud tenant hostname and other properties to your deployment:

    FAPI config.json

    (Source: config-fapi.json)

    {
      "properties": {
        "asHostname": "myTenant.forgeblocks.com",
        "gatewayOAuth2ClientId": "gateway-oauth2-client",
        "gatewayIdmUsername": "gateway-idm-user",
        "realm": "alpha",
        "tenantHostname": "myTenant.forgeblocks.com",
        "trustedDirectoryJwksUrl": "http://trustdir.example.com:9080/jwkms/testdirectory/jwks"
      },
      "handler": "_router",
      "heap": [
        {
          "name": "_router",
          "type": "Router",
          "config": {
            "directory": "${openig.configDirectory}/routes",
            "defaultHandler": {
              "type": "DispatchHandler",
              "config": {
                "bindings": [
                  {
                    "condition": "${request.method == 'GET' and request.uri.path == '/'}",
                    "handler": {
                      "type": "WelcomeHandler"
                    }
                  },
                  {
                    "condition": "${request.uri.path == '/'}",
                    "handler": {
                      "type": "StaticResponseHandler",
                      "config": {
                        "status": 405,
                        "reason": "Method Not Allowed"
                      }
                    }
                  },
                  {
                    "handler": {
                      "type": "StaticResponseHandler",
                      "config": {
                        "status": 404,
                        "reason": "Not Found"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "name": "capture",
          "type": "CaptureDecorator",
          "config": {
            "captureEntity": true
          }
        },
        {
          "name": "PlatformReverseProxyHandler",
          "comment": "Add a transaction ID header for calls to platform services",
          "type": "Chain",
          "config": {
            "filters": [
              "TransactionIdOutboundFilter"
            ],
            "handler": "ReverseProxyHandler"
          }
        },
        {
          "name": "SystemAndEnvSecretStore",
          "type": "SystemAndEnvSecretStore"
        },
        {
          "name": "IdmClientHandler",
          "type": "Chain",
          "config": {
            "filters": [
              {
                "type": "ResourceOwnerOAuth2ClientFilter",
                "config": {
                  "tokenEndpoint": "https://&{tenantHostname}/am/oauth2/realms/root/realms/&{realm}/access_token",
                  "username": "&{gatewayIdmUsername}",
                  "passwordSecretId": "gateway.idm.password",
                  "secretsProvider": "SystemAndEnvSecretStore",
                  "scopes": [
                    "fr:idm:*"
                  ],
                  "endpointHandler": {
                    "type": "Chain",
                    "config": {
                      "handler": "ForgeRockClientHandler",
                      "filters": [
                        {
                          "type": "ClientSecretBasicAuthenticationFilter",
                          "config": {
                            "clientId": "&{gatewayOAuth2ClientId}",
                            "clientSecretId": "gateway.oauth2.client.secret",
                            "secretsProvider": "SystemAndEnvSecretStore"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            ],
            "handler": "ForgeRockClientHandler"
          }
        },
        {
          "name": "JwkSetService",
          "type": "CachingJwkSetService",
          "config": {
            "cacheMaxSize": 500,
            "cacheTimeout": "24 hours"
          }
        },
        {
          "name": "TrustedDirectoryService",
          "type": "TrustedDirectoryService",
          "config": {
            "trustedDirectories": [
              "TestTrustedDirectory"
            ]
          }
        },
        {
          "name": "TestTrustedDirectory",
          "type": "TrustedDirectory",
          "config": {
            "issuer": "FAPI Test Trusted Directory",
            "softwareStatementClaims": {
              "organisationIdClaimName": "org_id",
              "organisationNameClaimName": "org_name",
              "softwareIdClaimName": "software_id",
              "clientNameClaimName": "software_client_name",
              "redirectUrisClaimName": "software_redirect_uris",
              "rolesClaimName": "software_roles",
              "_comment": "If your clients publish JWKs, use jwksUriClaimName instead of jwksClaimName.",
              "jwksClaimName": "software_jwks"
            },
            "secretsProvider": {
              "type": "SecretsProvider",
              "config": {
                "stores": [
                  {
                    "type": "JwkSetSecretStore",
                    "config": {
                      "jwkUrl": "&{trustedDirectoryJwksUrl}"
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "name": "IdmService",
          "type": "IdmService",
          "config": {
            "baseEndpoint": "https://&{tenantHostname}/openidm",
            "endpointHandler": "IdmClientHandler"
          }
        },
        {
          "name": "IdmApiClientService",
          "type": "IdmApiClientService",
          "config": {
            "idmService": "IdmService",
            "jwkSetService": "JwkSetService"
          }
        },
        {
          "name": "IdmApiClientOrganisationService",
          "type": "IdmApiClientOrganisationService",
          "config": {
            "idmService": "IdmService"
          }
        },
        {
          "name": "AsJwkSecretsProvider",
          "type": "SecretsProvider",
          "config": {
            "stores": [
              {
                "type": "JwkSetSecretStore",
                "config": {
                  "jwkUrl": "https://&{tenantHostname}/am/oauth2/realms/root/realms/&{realm}/connect/jwk_uri"
                }
              }
            ]
          }
        }
      ],
      "session": {
        "type": "JwtSessionManager"
      }
    }

    This file includes the settings for the trusted directory and access to Advanced Identity Cloud identity management services. If your clients publish JWKs, use jwksUriClaimName instead of jwksClaimName in the trusted directory configuration. This example uses jwksClaimName and static JWKs for the test client profiles, where the clients aren’t publicly accessible.

    Whenever you change this file, restart PingGateway to reload the configuration.

  4. Export secrets the PingGateway configuration takes from the environment.

    export GATEWAY_OAUTH2_CLIENT_SECRET='cGFzc3dvcmQ='  # Base64-encoding of `password`
    export GATEWAY_IDM_PASSWORD='U2VjcmV0MTIh'          # Base64-encoding of `Secret12!`

    These secrets correspond to the following properties in config.json:

    • gateway.oauth2.client.secret for the OAuth 2.0 client account gatewayOAuth2ClientId.

    • gateway.idm.password for the identity management user account gatewayIdmUsername.

    In production deployments, use a secure secret store.

  5. Restart PingGateway to load the new configuration.

You have successfully configured the base settings for PingGateway to support FAPI.

Routes to protect the authorization server

PingGateway protects access to these Advanced Identity Cloud authorization server endpoints to enforce FAPI compliance:

  • The dynamic client registration endpoint (/oauth2/register)

  • The access token endpoint (/oauth2/access_token)

  • The Pushed Authorization Request (PAR) endpoint (/oauth2/par)

  • The authorization endpoint (/oauth2/authorize)

  • The well-known configuration endpoint (/.well-known/openid-configuration)

Each of these corresponds to a PingGateway route for access to the authorization server. Make sure API clients and conformance tests go through these routes, not directly to Advanced Identity Cloud.

To the routes folder of the PingGateway configuration:

  1. Add a route to ensure compliant registration requests:

    When using a public trusted directory rather than the test trusted directory, don’t use allowPingIssuedTestCerts in the FapiDcrFilterChain.

  2. Add a route to ensure compliant access token requests:

  3. Add a route to ensure compliant PAR requests:

  4. Add a route to ensure compliant authorize requests:

  5. Add a route to ensure compliant well-known metadata requests: fapi-28-as-metadata.json.

You have successfully configured the routes for PingGateway to protect the Advanced Identity Cloud authorization server.

Resource server routes

In production systems, as shown in Deployment, use a separate PingGateway server with separate routes for each resource server role.

In this tutorial, this PingGateway server also acts as a resource server. You create a resource server route for this role.

  1. Add this script to the scripts/groovy folder of the PingGateway configuration: ExampleRsApiResponseHandler.groovy.

  2. Add this route to the routes folder of the PingGateway configuration: fapi-01-rs-example-protected-api.json.

You have successfully configured the resource server route for PingGateway. Create as many routes as needed for the resource server roles PingGateway plays.

Default route

Add the following default route to the routes folder of the PingGateway configuration: fapi-99-platform-passthrough.json.

This route lets requests like those for end-user sign on pass through unchanged.