PingGateway

The FAPI trusted directory

In a FAPI-based ecosystem, a central authority operates a trusted directory. Although not required for FAPI conformance testing, the trusted directory is a critical component of the ecosystem.

The trusted directory records the keys and other metadata for API clients of trusted organizations. The central authority vets the participants and restricts access to trusted organizations and their API clients. Each trusted organization registers its API clients with the trusted directory before registering them with other organizations.

When an API client registers with another organization, the trusted directory ensures only a trusted API clients can do so:

  • Each organization configures their authorization server to use the trusted directory.

    When an API client registers dynamically with the authorization server, it presents a signed software statement assertion (SSA) from the trusted directory.

    The authorization server validates the software statement including the signature and issuer.

  • The software statement from the trusted directory includes the client’s keys and other metadata. It vouches for the API client as a trusted participant in the ecosystem.

    The trusted directory issues software statement assertions only to trusted API clients of trusted organizations.

  • The trusted directory can revoke an API client’s participation in the ecosystem.

Production deployments in FAPI-based ecosystems require the trusted directory. In each participating organization, the authorization server depends on SSAs from the central trusted directory. PingGateway protects dynamic client registration (DCR) requests to the authorization server, allowing only trusted API clients to register.

Make sure you understand the trusted directory’s role in API client registration. This page explains how to use the sample trusted directory for this tutorial.

Run the sample trusted directory

If you plan to deploy FAPI components on Kubernetes using Helm, generate a keystore for the sample as described in this section, then follow the instructions in Deploy FAPI with Kubernetes and Helm instead.

This feature requires PingGateway 2026.6 or later.

The sample trusted directory is a Docker image available as gcr.io/forgerock-io/ig-sample-trusted-directory:2026.6.0. You can run it locally or in a test environment accessible to PingGateway.

The sample trusted directory requires the following:

  • A fully qualified domain name (FQDN).

    If you’re running the sample trusted directory locally, add an alias to your hosts file:

    127.0.0.1	trustdir.example.com
  • CA keys for TLS and another key pair for signing.

    Generate your own for testing.

  • A configuration file for the sample trusted directory.

    Save a configuration file such as the following example as trusted-directory/config/config.yml:

    server:
      port: 8080
    
    trustedDirectory:
      issuerName: PingGateway Sample Trusted Directory
      fqdn: trustdir.example.com:8080
    
      signing:
        keystorePath: /var/trusted-directory/secrets/trusted-directory-keystore.p12
        keystoreType: PKCS12
        keyAlias: jwt-signer
    
      ca:
        keystorePath: /var/trusted-directory/secrets/trusted-directory-keystore.p12
        keystoreType: PKCS12
        keyAlias: ca
        certSigningAlg: SHA256withRSA
    
      storageFilePath: /var/trusted-directory/data/trusted-directory-jwks.json
    
      cert:
        keySize: 4096
        validityDays: 365

    Notice the issuer name is PingGateway Sample Trusted Directory. When the sample trusted directory issues a software statement, it uses this issuer name. You’ll need it when you register the trusted directory in PingOne Advanced Identity Cloud.

The following sample script runs the sample trusted directory as a Docker container listening on port 9080. It generates the keys in a ./secrets folder using the keytool command on the first run:

#!/usr/bin/env bash

DOCKER_IMAGE="gcr.io/forgerock-io/ig-sample-trusted-directory:2026.6.0"
SECRETS_DIR="trusted-directory/secrets"
KEYSTORE_FILE="${SECRETS_DIR}/trusted-directory-keystore.p12"

checkForDocker() {
  OUT=$(docker --version)
  RC=$?
  if [ $RC -ne 0 ]; then
      echo "### 'docker' command not found. The sample trusted directory requires Docker to run."
      exit $RC
  else
    echo "Found ${OUT}"
  fi
}

checkForKeytool() {
  OUT=$(keytool --version)
  RC=$?
  if [ $RC -ne 0 ]; then
      echo "### 'keytool' command not found. The sample trusted directory requires keytool to create test keys."
      exit $RC
  else
    echo "Found ${OUT}"
  fi
}

createKeys() {
  echo "### Creating trusted directory keystore ${PWD}/${KEYSTORE_FILE}..."
  echo
  mkdir -p "${SECRETS_DIR}"

  echo "### Generating CA keys..."
  echo
  keytool -keystore "${KEYSTORE_FILE}" -storetype PKCS12 \
          -genkeypair -alias ca -ext bc=ca:true -keyalg RSA -keysize 4096 -validity 365 \
          -dname "CN=PingGateway Sample Trusted Directory Root CA" \
          -storepass changeit -keypass changeit

  echo
  echo "### Generating JWT Signing keys..."
  echo
  keytool -keystore "${KEYSTORE_FILE}" -storetype PKCS12 \
          -genkeypair -alias jwt-signer -keyalg RSA -keysize 4096 -validity 365 \
          -dname "CN=PingGateway Sample Trusted Directory JWT Signer" \
          -storepass changeit -keypass changeit

  echo
  echo "### Listing keystore contents..."
  echo
  keytool -keystore "${KEYSTORE_FILE}" -storetype PKCS12 -list -storepass changeit

  echo
  echo "### Exporting CA certificate..."
  echo
  keytool -exportcert -alias ca -rfc -file "${SECRETS_DIR}/trusted-directory-ca.pem" \
          -keystore "${KEYSTORE_FILE}" -storetype PKCS12 -storepass changeit

  echo
  echo "### Done."
}

initializeDataDir() {
    echo "### Initializing trusted directory data directory..."
    echo
    mkdir -p "${PWD}/trusted-directory/data"
}

runTestTrustedDirectory() {
  echo "### Running sample trusted directory in Docker..."
  echo

  docker pull "${DOCKER_IMAGE}"

  docker run \
  --detach \
  --init \
  --tty \
  --rm \
  --mount type=bind,src="${PWD}/trusted-directory",dst="/var/trusted-directory" \
  --env TD_CONFIG_PATH="/var/trusted-directory/config/config.yml" \
  --env TD_SIGNING_KEYSTORE_PWD=changeit \
  --env TD_SIGNING_KEYSTORE_KEY_PWD=changeit \
  --env TD_CA_KEYSTORE_PWD=changeit \
  --env TD_CA_KEYSTORE_KEY_PWD=changeit \
  --name sample-trusted-directory \
  --publish 9080:8080 \
  "${DOCKER_IMAGE}"
}

checkForDocker

if [ ! -f "${KEYSTORE_FILE}" ]; then
  checkForKeytool
  createKeys
else
  echo "### Sample trusted directory keystore already exists, skipping key creation."
  echo
fi

initializeDataDir

runTestTrustedDirectory

You’ve successfully started the sample trusted directory.

Update the PingGateway trust store

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

The following example generates imports the sample trusted directory’s CA certificate from a file named trusted-directory/secrets/trusted-directory-ca.pem into secrets/keystore.p12:

keytool -keystore secrets/keystore.p12 -storetype PKCS12 \
        -importcert -trustcacerts -alias ca -rfc \
        -file trusted-directory/secrets/trusted-directory-ca.pem -storepass:file secrets/keystore.pin -noprompt

Register the sample trusted directory

In PingOne Advanced Identity Cloud the trusted directory plays the role of a software publisher. The authorization server uses a software publisher agent profile with the trusted directory’s metadata and keys. It uses the profile to validate and trust software statements (SSAs) the API clients present during registration.

In production deployments, use your FAPI ecosystem’s official trusted directory service instead.
  1. In the Advanced Identity Cloud admin UI, click open_in_new Native Consoles > Access Management to open the AM admin UI.

  2. Go to Services > Applications > Software Publisher > Add Software Publisher Agent and configure the account for the trusted directory:

    Field Value

    Agent ID

    Trusted Directory

    Software publisher issuer

    PingGateway Sample Trusted Directory

    Software statement signing Algorithm

    PS256

    Public key selector

    JWKs_URI

    Json Web Key URI

  3. Click Save Changes.

You have successfully configured the software publisher account for the sample trusted directory.

Register the API clients

The FAPI conformance tests use two API clients, each with different keys and the same redirect URIs.

Create an organization

  1. Go to the trusted directory Organizations UI page at http://trustdir.example.com:9080/ui/orgs, and click + New Organization.

  2. Name the new organization Test organization and click Create Organization.

You’ve successfully created a test organization.

Create the API clients

Create both API clients by following these steps for each client:

  1. On the Organizations UI page, click > in the Actions for the Test organization.

  2. Click + New Software and use the following hints for the settings before clicking Create Software:

    Field Example values

    Name

    FAPI test client 1 or FAPI test client 2

    Description

    FAPI test client for conformance tests

    Redirect URIs

    https://www.certification.openid.net/test/a/pinggateway/callback https://www.certification.openid.net/test/a/pinggateway/callback?dummy1=lorem&dummy2=ipsum

    Change pinggateway in the URLs to a path element that’s unique to your test deployment.

    Roles

    DATA

    Terms of Service URI

    https://example.com/terms-of-service

    Policy URI

    https://example.com/policy

    After you create the API clients, they show up in the UI software list for the test organization:

    The UI software list shows the API clients.

You’ve successfully created the test API clients.

Register each API client

Register each API client through PingGateway into PingOne Advanced Identity Cloud.

You provide the SSA and other client metadata as a client-signed JWT in the POST data of the registration request. Make sure you use the SSA and your client-signed JWT before either expires.

  1. Get the SSA for the API client.

    1. Go to the trusted directory UI Test organization page under http://trustdir.example.com:9080/ui/orgs.

    2. Click the > in the Actions for test API client software to open its page.

    3. Click Generate SSA and copy the generated JWT.

  2. Prepare the JSON payload for the client registration request.

    Here’s an example payload template for the first API client:

    {
      "client_name": "FAPI test client 1",
      "issuer": "PingGateway Sample Trusted Directory",
      "grant_types": [
        "authorization_code",
        "client_credentials",
        "refresh_token"
      ],
      "id_token_signed_response_alg": "PS256",
      "iss": "test-client-1",
      "redirect_uris": [
        "https://www.certification.openid.net/test/a/pinggateway/callback",
        "https://www.certification.openid.net/test/a/pinggateway/callback?dummy1=lorem&dummy2=ipsum"
      ],
      "request_object_signing_alg": "PS256",
      "response_types": [
        "code id_token"
      ],
      "scope": "openid",
      "token_endpoint_auth_method": "tls_client_auth",
      "tls_client_auth_subject_dn": "CN=Test organization",
      "token_endpoint_auth_signing_alg": "PS256",
      "exp": <expiration-time>,
      "iat": <issued-at-time>,
      "nbf": <issued-at-time>,
      "software_statement": "<SSA-JWT>"
    }
    1. Copy the SSA JWT into the template as the software_statement value.

    2. Decode the SSA JWT using the Ping Identity JWT Decoder online.

    3. Copy the exp and iat claim values from the decoded SSA JWT into the template.

      Use the value of iat to set the nbf (not before) claim.

  3. Generate the signed JWT for the client registration request:

    1. Go to https://www.jwt.io/, select JWT encoder, and fill the form:

      Field Use

      Header

      { "alg": "PS256", "typ": "JWT" }

      Payload

      The completed JSON template from the previous step.

      Sign JWT

      Find the sig key in the JWK set you received when requesting the keys from the trusted directory.

      Copy and paste the JSON object just for that key, not the whole JWK set.

      Private key format

      JWK

  4. Copy the resulting signed JWT to your clipboard.

  5. Register the API client in PingOne Advanced Identity Cloud using the SSA JWT from the trusted directory for dynamic client registration, (DCR).

    The following example registers an API client through PingGateway. Adapt this to match the realm and redirect URIs you use for testing:

    $ curl \
    --request POST \
    --url 'https://gateway.example.com:8443/am/oauth2/realms/root/realms/alpha/register' \
    --cacert "$HOME/path/to/secrets/gateway.pem" \
    --header 'Content-Type: application/json' \
    --header "ssl-client-cert: ${SSL_CLIENT_CERT}" \
    --data "<client-signed-jwt>"
  6. Save the JSON response.

    It includes the client ID, client secret, and important metadata required for conformance testing.

You have successfully registered the API client using the sample trusted directory. Repeat the steps for the other API client unless you have already done so.

When you’ve successfully registered both API clients, you can proceed to the conformance tests.