1. Background

Apache Knox Gateway has traditionally acted as a federation client, delegating authentication to external Identity Providers (IdPs) via pac4j. Through pac4j, Knox can integrate with a wide range of authentication systems including CAS, OAuth 2.0, SAML, and OpenID Connect (OIDC). In this model, Knox is strictly a relying party (client) and never an identity provider itself.

Many modern architectures, however, expect a centralized OIDC Provider (OP) that issues tokens to downstream services. Products like Okta, Azure AD, Keycloak are commonly used for this purpose, but introducing and operating a separate IdP is not always desirable, especially in Hadoop-centric or Knox-centric deployments.

This proposal addresses this gap by enabling Apache Knox to act as an OIDC Provider while still retaining its federation capabilities. With KnoxIDF:

This makes Knox both:

2. Functional Requirements

  1. OIDC Provider Capabilities

  2. Supported OAuth 2.0 / OIDC Flows

  3. Federation Support

  4. Topology Integration

  5. Persistence


As part of preparing this document I already implemented a POC version of this new feature, but this is still a POC. Going forward I share what/why I implemented and conclude with some possible improvement ideas.

3. Design

3.1 New Knox Service: gateway-service-knox-idf

A new Knox service named gateway-service-knox-idf shall be introduced.

Key characteristics:

This design keeps KnoxIDF modular and avoids changes to existing gateway authentication flows.



3.2 Login page changes

In the case of Authorization Code flow, the login page will display any pre-configured federated OPs, if the feature is enabled. To do that, the existing SSOCookieProvider needed to be updated to fetch this data from the KNOXIDF service configuration and pass it to Knox's login.html, where new JS code takes care of showing the alternative login path like this:

This way, end-users can decide if they want to use Knox's own authentication providers (PAM, LDAP, SAML, Kerberos, etc...) or if they want to identify themselves using the external OIDC Provider (as part of the POC, only Keycloak is supported/tested, but this should be easily extendable to other OIDC providers such as Okta, AzureAD, PingFederate,...).



3.3 REST API Endpoints

KnoxIDF exposes a set of REST endpoints aligned with standard OIDC expectations. These endpoints are compatible with clients configured for Keycloak-like providers.

Key categories include:



3.4 Federation Architecture

When federation is enabled:

Federation is implemented as a token brokering mechanism:

  1. Client initiates an OAuth/OIDC flow against Knox.

  2. Knox delegates authentication to a configured external OP (e.g., Keycloak).

  3. External OP authenticates the user and returns an ID token.

  4. Knox:

This ensures downstream services only need to trust Knox, regardless of how authentication was performed upstream.



3.4.1 Database Design


Federated identity data is persisted using new database tables:

federated_identity

Stores the core identity mapping between Knox and the external OP.

Columns:

federated_identity_attr

Stores individual attributes extracted from the federated ID token.

Columns:

Design considerations:

3.4.2 JdbcFederatedIdentityService configuration


By default, the storage and the corresponding FederatedIdentityService points to an empty implementation, which returns default values (empty lists, blank String instances, etc...). It's the end-user's responsibility to explicitly configure this feature (when federation is enabled with at least one federated OP) by setting the relevant federated identity service implementation in the gateway-site.xml  as follows:

    <property>
        <name>gateway.service.knoxidf.federatedidentity.impl</name>
        <value>org.apache.knox.gateway.services.knoxidf.federation.JdbcFederatedIdentityService</value>
    </property>

Failing to do this, the token generation and user info fetch actions will return an error indicating that the federated identity could not be found.



3.5 Advanced User Parameter Mappings

KnoxIDF supports advanced mechanisms for enriching ID tokens with additional user-related claims. These mechanisms allow administrators to inject static claims or dynamically resolve user attributes from external systems, enabling flexible identity and attribute modeling comparable to established OIDC providers such as Keycloak.

Two approaches are currently supported: hard-coded ID token claims and pluggable user parameter providers.



3.5.1 Hard-coded ID Token Claims

Similar to other, well-known OIDPs, KnoxIDF allows administrators to define static, hard-coded claims that are automatically included in generated ID tokens. These claims are independent of the authenticated user and are useful for injecting fixed metadata such as roles, scopes, or system-level identifiers.

Hard-coded claim mappings are configured using the knox.token.hardcoded.claim.mappings property in the KNOXTOKEN service definition.

Example configuration:


      <param>
        <name>knox.token.hardcoded.claim.mappings</name>
        <value>principal_roles=admin;scope=openid;principal_id=0;principal_name=root</value>
      </param>


Each key-value pair represents an ID token claim name and its corresponding static value. All configured claims are added to the issued ID token during token generation.


3.5.2 User Parameters Provider

To support dynamic user-specific claim resolution, KnoxIDF introduces a pluggable user parameter resolution mechanism via the UserParamsProvider interface. This abstraction allows KnoxIDF to fetch OIDC-related user attributes from external systems at token issuance time.

The interface definition is as follows:

public interface UserParamsProvider {

    /**
     * Fetches OIDC parameters for the given subject name.
     *
     * @param subjectName The user login/ID (e.g., "sam").
     * @return a map of OIDC parameters (e.g., email, name, roles)
     */
    Map<String, Object> getParamsFor(String subjectName, String scope);
}

As part of this effort, a first concrete implementation has been added: LdapUserParamsProvider. This implementation resolves user attributes from an LDAP directory and maps them into ID token claims.

The LDAP provider currently uses a fixed configuration model (subject to future enhancement) and retrieves a predefined set of attributes (e.g., cn, sn, givenName, mail) for the authenticated user.

The LDAP connection endpoint is configured via the following topology parameter, which must be defined in the KNOXIDF service:

       <param>
          <name>user.params.provider.ldap.url</name>
          <value>ldap://host.docker.internal:33389</value>
       </param>


At token issuance time, KnoxIDF invokes the configured UserParamsProvider to enrich the ID token with dynamically resolved user claims, optionally filtered by the requested OIDC scope.



3.6 Consent Page

As part of the Authorization Code flow, KnoxIDF introduces a consent page to ensure that end-users explicitly approve the scopes requested by a client application.

Key characteristics:

This design aligns with standard OIDC provider behavior and provides a simple but essential layer of user control over granted permissions.


Sequence:



4. Testing

4.0 Knox setup

At the time of this document being written, the Knox IDF code is sitting on a branch called knox_idf (keeping it in sync with master) in the Apache Knox repo. The following steps will help you to start and configure Knox to handle KnoxIDF-related API calls.

4.0.1 - Build Knox

git clone https://github.com/apache/knox.git

git checkout knox_idf

mvn -DskipTests -Dcheckstyle.skip=true -Dfindbugs.skip=true -Dpmd.skip=true -Drat.skip -Dspotbugs.skip=true -Dforbiddenapis.skip=true -Ppackage clean install

4.0.2 - Install Knox

Whenever I test Knox functionalities, I never use the built-in ant targets (such as ant install-test-home), instead I unzip the generated Knox deliverables into my test folder and use a Terminal window to create the required secrets and (re-)start Knox. I use the following bash script that does the job for me:

#!/bin/bash

KNOX_SOURCE=/Users/smolnar/projects/knox
KNOX_VERSION=3.0.0-SNAPSHOT
KNOX_HOME=/Users/smolnar/test/knoxGateway

function redeployKnoxLocal() {
    echo "Redeploying Knox $KNOX_VERSION ..."
    cd $KNOX_HOME
    echo "Stopping Knox (if needed)..."
    bin/gateway.sh stop
    echo "Removing old Knox deployment files..."
    rm -rf $KNOX_HOME/*
    echo "Unpacking Knox artifacts into $KNOX_HOME ..."
    unzip -q $KNOX_SOURCE/target/$KNOX_VERSION/knox-$KNOX_VERSION.zip -d $KNOX_HOME
    mv knox-$KNOX_VERSION/* .
    rm -rf mv knox-$KNOX_VERSION
    echo "Creating master secret..."
    bin/knoxcli.sh create-master --master gateway
    echo "Creating knox.token.hash.key alias..."
    bin/knoxcli.sh create-alias knox.token.hash.key --value B5oxlx9M4h4MhaGakj8k7Q2fbmPzo6h9te8dWTHs5Mg
    echo "Creating DB aliases..."
    bin/knoxcli.sh create-alias gateway_database_user --value knox
    bin/knoxcli.sh create-alias gateway_database_password --value knox
    echo "Starting Knox..."
    bin/gateway.sh start
}

Once this is done, you'll need to edit the gateway-site.xml config file in your $KNOX_HOME/conf  folder and add the following params:

    <property>
        <name>ssl.enabled</name>
        <value>false</value>
    </property>
     <property>
        <name>gateway.service.knoxidffederatedidentity.impl</name>
        <value>org.apache.knox.gateway.services.knoxidf.federation.JdbcFederatedIdentityService</value>
    </property>

    <!-- Optionally, setup your PostgreSQL DB here -->
    <property>
        <name>gateway.service.tokenstate.impl</name>
        <value>org.apache.knox.gateway.services.token.impl.JDBCTokenStateService</value>
    </property>
     <property>
        <name>gateway.database.type</name>
        <value>postgresql</value>
    </property>
    <property>
        <name>gateway.database.connection.url</name>
        <value>jdbc:postgresql://localhost:5432/postgres</value>
    </property>

When all good, you need to restart Knox:


cd $KNOX_HOME; bin/gateway.sh restart 


4.0.3 - Install KnoxIDF topologies

You'll see that testing the client credentials and authorization code flows, we'll need 2 dedicated Knox topologies:

You have to copy them into $KNOX_HOME/conf/topologies and Knox will deploy them for you in a couple seconds.

<?xml version="1.0" encoding="utf-8"?>
<topology>
    <gateway>
      <provider>
         <role>federation</role>
         <name>SSOCookieProvider</name>
         <enabled>true</enabled>
         <param>
            <name>knox.token.exp.server-managed</name>
            <value>false</value>
         </param>
         <param>
            <name>sso.unauthenticated.path.list</name>
            <value>/api/v1/websso/federated/op,/knoxidf/api/v1/.well-known/openid-configuration,/knoxidf/api/v1/client/register,/knoxidf/api/v1/authorize/callback,/knoxidf/api/v1/jwks</value>
         </param>
        <param>
            <name>jwt.expected.issuer</name>
            <value>http://www.local.com:8443/gateway/knoxidf-sso/knoxidf</value>
        </param>
      </provider>
      <provider>
            <role>identity-assertion</role>
            <name>Default</name>
            <enabled>true</enabled>
      </provider>
    </gateway>

    <service>
        <role>KNOXIDF</role>
       <param>
          <name>knox.token.ttl</name>
          <value>60000</value>  <!-- 1 min -->
       </param>
       <param>
          <name>token.exchange.topology.name</name>
          <value>knoxidf-token</value>
       </param>

       <!-- Federated OPs -->
       <param>
          <name>federated.op.names</name>
          <value>KeyCloak,Auth0</value>
       </param>

       <!-- KeyCloak -->
       <param>
          <name>federated.op.KeyCloak.enabled</name>
          <value>true</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.clientId</name>
          <value>knox-proxy</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.clientSecret</name>
          <value>eGUrfiYShueXAyGC3ZZAMNScGGWBuyLb</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.authorize.endpoint</name>
          <value>http://www.local.com:8081/realms/demo/protocol/openid-connect/auth</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.authorize.callback</name>
          <value>http://www.local.com:8443/gateway/knoxidf-sso/knoxidf/api/v1/authorize/callback</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.token.endpoint</name>
          <value>http://www.local.com:8081/realms/demo/protocol/openid-connect/token</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.userinfo.endpoint</name>
          <value>http://www.local.com:8081/realms/demo/protocol/openid-connect/userinfo</value>
       </param>
       <param>
          <name>federated.op.KeyCloak.discovery.endpoint</name>
          <value>http://www.local.com:8081/realms/demo/.well-known/openid-configuration</value>
       </param>
       <!-- Auth0 -->
       <param>
          <name>federated.op.Auth0.enabled</name>
          <value>true</value>
       </param>
       <param>
          <name>federated.op.Auth0.clientId</name>
          <value>$YOUR_CLIENT_ID</value>
       </param>
       <param>
          <name>federated.op.Auth0.clientSecret</name>
          <value>$YOUR_CLIENT_SECRET</value>
       </param>
       <param>
          <name>federated.op.Auth0.authorize.endpoint</name>
          <value>https://dev-4nx2z7mc7rc6vonp.us.auth0.com/authorize</value>
       </param>
       <param>
          <name>federated.op.Auth0.authorize.callback</name>
          <value>http://www.local.com:8443/gateway/knoxidf-sso/knoxidf/api/v1/authorize/callback</value>
       </param>
       <param>
          <name>federated.op.Auth0.token.endpoint</name>
          <value>https://dev-4nx2z7mc7rc6vonp.us.auth0.com/oauth/token</value>
       </param>
       <param>
          <name>federated.op.Auth0.userinfo.endpoint</name>
          <value>https://dev-4nx2z7mc7rc6vonp.us.auth0.com/userinfo</value>
       </param>
       <param>
          <name>federated.op.Auth0.discovery.endpoint</name>
          <value>https://dev-4nx2z7mc7rc6vonp.us.auth0.com/.well-known/openid-configuration</value>
       </param>
       
       <param>
          <name>user.params.provider.ldap.url</name>
          <value>ldap://www.local.com:33389</value>
       </param>
    </service>
</topology>
<?xml version="1.0" encoding="utf-8"?>
<topology>
    <gateway>
      <provider>
         <role>federation</role>
         <name>JWTProvider</name>
         <enabled>true</enabled>
         <param>
            <name>knox.token.exp.server-managed</name>
            <value>true</value>
         </param>
      </provider>
    </gateway>

    <service>
        <role>KNOXIDF</role>
       <param>
          <name>knox.token.ttl</name>
          <value>120000</value> <!-- 2 mins -->
       </param>
       <param>
          <name>knox.token.issuer</name>
          <value>http://www.local.com:8443/gateway/knoxidf-sso/knoxidf</value>
       </param>
       <param>
          <name>user.params.provider.ldap.url</name>
          <value>ldap://www.local.com:33389</value>
       </param>
    </service>
</topology>

4.0.4 Client registration

After all the above steps, we now arrived to the point where you need to register a client to Knox IDF. It's as simple as issuing the following curl request:

$ curl -ik -X POST http://localhost:8443/gateway/knoxidf-sso/knoxidf/api/v1/client/register \
> -H "Content-Type: application/x-www-form-urlencoded" \
> -H "X-XSRF-Header: valid" \
> -d "redirect_uris=http://localhost:8443/gateway/*,http://host.docker.internal:8443/*,http://host.docker.internal:9080/*&allowed_scopes=openid,profile"
HTTP/1.1 200 OK
Date: Mon, 27 Apr 2026 14:28:29 GMT
Content-Type: text/plain
Content-Length: 357

{"client_secret":"WXpBNU5tUTFPVGN0WXpkaVppMDBaVEF3TFdKaE0yUXRabUl6T0RJd01ERTNaRE0zOjpORGc0WkRkaFlqTXRObUl4TmkwME5tWTVMVGt4WmpVdE1HUTJPV00xWVdOallqWTQ=","redirect_uris":"http://localhost:8443/gateway/*,http://host.docker.internal:8443/*,http://host.docker.internal:9080/*","client_id":"c096d597-c7bf-4e00-ba3d-fb3820017d37","allowed_scopes":"openid,profile"}

You'll have to use the acquired client credentials (clientId/clientSecret pair) going forward.

4.1 Testing – Polaris

To verify KnoxIDF’s compatibility with the Client Credentials flow, we leveraged Apache Polaris using its existing Keycloak integration as a reference. The official Polaris documentation for Keycloak is available here: Polaris Keycloak IdP Guide.

Pre-requisites

Before running the tests, the following tools and setup are required:


Test Setup

  1. Register a client  - already done in 4.04 (my client id is c096d597-c7bf-4e00-ba3d-fb3820017d37, it's a never-expiring token).
  2. Create a KnoxIDF-specific Docker environment

  3. Run the KnoxIDF environment

    cd ~/projects/polaris docker compose -f getting-started/polaris_knoxidf/docker-compose.yml up



Running the Tests

Sample result:

$ clear; ./polaris_knoxidf_test.sh 

==================================================================
  OBTAINING TOKEN FROM KNOXIDF
==================================================================
KnoxIDF token: eyJqa3UiOiJodHRwOi8vbG9jYWxob3N0Ojg0NDMvZ2F0ZXdheS9rbm94LWF3Yy10b2tlbi9rbm94dG9rZW4vYXBpL3YxL2p3a3MuanNvbiIsImtpZCI6ImxOelF5WnAtREhaLWlKVVF1M0c0MExucWFFNWNvaWRQaWZkRGRHTDlRWXciLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkMzg4NGZmNS1hZTA3LTQ1YzgtOTM4NS0yN2UzZDliYTE4NjMiLCJraWQiOiJsTnpReVpwLURIWi1pSlVRdTNHNDBMbnFhRTVjb2lkUGlmZERkR0w5UVl3IiwiaXNzIjoiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjg0NDMvZ2F0ZXdheS9rbm94LWF3Yy10b2tlbi9rbm94Y2xvYWsiLCJwcmluY2lwYWxfbmFtZSI6InJvb3QiLCJwcmluY2lwYWxfcm9sZXMiOiJhZG1pbiIsInByaW5jaXBhbF9pZCI6IjAiLCJqa3UiOiJodHRwOi8vbG9jYWxob3N0Ojg0NDMvZ2F0ZXdheS9rbm94LWF3Yy10b2tlbi9rbm94dG9rZW4vYXBpL3YxL2p3a3MuanNvbiIsInNjb3BlIjoib3BlbmlkIiwiZXhwIjoxNzY3NjIzNzQxLCJtYW5hZ2VkLnRva2VuIjoidHJ1ZSIsImlhdCI6MTc2NzYyMDE0MSwia25veC5pZCI6IjkzMTk1ZjY2LTM5ZjktNDg2NC05Y2E1LWU2OTY1YzFmYmQ4NyJ9.Z5TRCLj89CpFN47-gua5SAzJMPzVwkyZmcCehwzbNM8biotuVhPA97MSI5ZrqhxMm03wLOcgit8kct2rnmTSSuZMgpUWGcs3_SeCPq0ZMQF0ETX5TqnR1wXdVJnD044RuM_Jam-OE60S1RNlZNupZJrbk5LkOH999tN_KYTdRZS94m2ZaYNoH4KckA50uRKTIBYN9N-kRqAdhGTTTfgKgNqs9py-nOratZSHFDDUjosF00w7E14VCEsL7QGUqyU-N8RuadT5BJ6H2rKOHfDdFNNszl3sExZ52rOWO030HdO0gyTsfDnY2n8rg4zUopHCRIOoUOqkFMWbsMHbmhx3XA


==================================================================
  OBTAINING POLARIS TOKENS (Internal + Mixed)
==================================================================
Polaris token (realm-internal): eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwb2xhcmlzIiwic3ViIjoicm9vdCIsImlhdCI6MTc2NzYyMDE0MSwiZXhwIjoxNzY3NjIzNzQxLCJqdGkiOiJhYTc2NDJiOC03OGYzLTQ2MTctODdiYy1mYzJlYTg5NWU2MzAiLCJhY3RpdmUiOnRydWUsImNsaWVudF9pZCI6InJvb3QiLCJwcmluY2lwYWxJZCI6MSwic2NvcGUiOiJQUklOQ0lQQUxfUk9MRTpBTEwifQ.IRndQQZ1iGQyfjA6OnQ5JoX7Cz84wHTgvwTMCqBPIaa1HvwPJlWy_0KGeMKuTg2YbhuU_Tbp8mxk88WymN3ObQNNAu6gA4pa_pOdk2Vs2PgIcSkevGIEWWt61UN6DJ5oiH5Cv6qnSkI0S3SVD7kDFxg3bacO8gZtE1P8I41sClmnsHBQWO7JuC4jQDwf06DEzhoR1SGQvKrmSYlCs2IOvBLZNviFQjey4YqC5hQsq853OBzO0k_8AGYLJf8xycrMpUW3zVGJYPb1Q5dbkx1ONiRjM3XPWInJ8Knt3T7gTv9Tw0BrOuFAMEXEHHVrxvpRr3PDfxAbx9IJWLuMTmLErw

Polaris token (realm-mixed)  : eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwb2xhcmlzIiwic3ViIjoicm9vdCIsImlhdCI6MTc2NzYyMDE0MSwiZXhwIjoxNzY3NjIzNzQxLCJqdGkiOiJjODIwN2IyZS03ZjVjLTQyNDMtODM5MS01ZDE3OWJmYWM5MmMiLCJhY3RpdmUiOnRydWUsImNsaWVudF9pZCI6InJvb3QiLCJwcmluY2lwYWxJZCI6MSwic2NvcGUiOiJQUklOQ0lQQUxfUk9MRTpBTEwifQ.hXBMe2D22liicaR02Ee6HWfTnaqV8IQY5qHC0uFc9gEQXgN9bSyhPp_-QEFF9Z4cKyfZe4JRogocBhR9TtXxdr2t-qoecBisbJXaEm9amrm0Q84BSoCQONdo8RNFPwi_5No9V7F8H_wVcRsPNuF_hKdrx4_hOv3kSRakwmKtuD0aAmDU2S4xq_pRSTyHytG91D99tiTrZCrfjrrG8DwKHRGpKW2ZkxYtZcjrZ1XUQXV7P__x7rE_IOxknaq6XNVYh2FyTiTMuJJ1iAmtF0yB-0cI9yo7F0XOuoKoGxzhywU5FoAqxkcAht0ItX0KcC5zEgzNiEoHnwgZijpmmJzudw


==================================================================
 TEST: Knox token → realm-internal (SHOULD FAIL)
==================================================================
✅ PASS: Got HTTP 401 as expected

==================================================================
 TEST: Knox token → realm-external (SHOULD SUCCEED)
==================================================================
✅ PASS: Got HTTP 200 as expected
Response JSON:
{
  "catalogs": [
    {
      "type": "INTERNAL",
      "name": "quickstart_catalog",
      "properties": {
        "default-base-location": "file:///var/tmp/quickstart_catalog/"
      },
      "createTimestamp": 1767620134745,
      "lastUpdateTimestamp": 1767620134745,
      "entityVersion": 1,
      "storageConfigInfo": {
        "storageType": "FILE",
        "allowedLocations": [
          "file:///var/tmp/quickstart_catalog/"
        ]
      }
    }
  ]
}

==================================================================
 TEST: Knox token → realm-mixed (SHOULD SUCCEED)
==================================================================
✅ PASS: Got HTTP 200 as expected
Response JSON:
{
  "catalogs": [
    {
      "type": "INTERNAL",
      "name": "quickstart_catalog",
      "properties": {
        "default-base-location": "file:///var/tmp/quickstart_catalog/"
      },
      "createTimestamp": 1767620136082,
      "lastUpdateTimestamp": 1767620136082,
      "entityVersion": 1,
      "storageConfigInfo": {
        "storageType": "FILE",
        "allowedLocations": [
          "file:///var/tmp/quickstart_catalog/"
        ]
      }
    }
  ]
}

==================================================================
 TEST: Polaris token (internal) → realm-internal (SHOULD SUCCEED)
==================================================================
✅ PASS: Got HTTP 200 as expected
Response JSON:
{
  "catalogs": [
    {
      "type": "INTERNAL",
      "name": "quickstart_catalog",
      "properties": {
        "default-base-location": "file:///var/tmp/quickstart_catalog/"
      },
      "createTimestamp": 1767620133351,
      "lastUpdateTimestamp": 1767620133351,
      "entityVersion": 1,
      "storageConfigInfo": {
        "storageType": "FILE",
        "allowedLocations": [
          "file:///var/tmp/quickstart_catalog/"
        ]
      }
    }
  ]
}

==================================================================
 TEST: Polaris token (mixed) → realm-mixed (SHOULD SUCCEED)
==================================================================
✅ PASS: Got HTTP 200 as expected
Response JSON:
{
  "catalogs": [
    {
      "type": "INTERNAL",
      "name": "quickstart_catalog",
      "properties": {
        "default-base-location": "file:///var/tmp/quickstart_catalog/"
      },
      "createTimestamp": 1767620136082,
      "lastUpdateTimestamp": 1767620136082,
      "entityVersion": 1,
      "storageConfigInfo": {
        "storageType": "FILE",
        "allowedLocations": [
          "file:///var/tmp/quickstart_catalog/"
        ]
      }
    }
  ]
}

==================================================================
 TEST: Polaris token (internal) → realm-mixed (SHOULD FAIL)
==================================================================
✅ PASS: Got HTTP 401 as expected

==================================================================
ALL TESTS COMPLETE
==================================================================



Result

Following these steps demonstrates that:



4.2 Testing – Authorization Code Flow with APISIX

To validate KnoxIDF as an Authorization Code provider, I used APISIX as a reverse proxy, replicating the standard Keycloak integration. This test confirms both standalone and federated operation modes.

Documentation Reference

The APISIX OIDC plugin documentation can be found in the official APISIX guides. These tests use the same configuration patterns outlined there, adapted for KnoxIDF as the OIDC provider. Some of the relevant docs are:



Pre-requisites

Before testing, the following components must be available and running:

  1. APISIX

  2. Keycloak (testing federation)

  3. Auth0 (testing federation)
    1. Register yourself in ww.auth0.com and create a test application. Mine is called KnoxProxy and looks like this:
    2. Create a test user. Mine is samauth0@sam.com

  4. Backend service

  5. KnoxIDF


Test Setup

1. Keycloak Route Verification

Route creation command:

curl  -X PUT 127.0.0.1:9180/apisix/admin/routes -H "X-Api-Key: edd1c9f034335f136f87ad84b625c8f1" -d '{
    "uri":"/*",
    "id": "apisix-keycloak-protected",
    "plugins":{
        "openid-connect":{
            "client_id":"apisix",
            "client_secret":"NixXgcMKEFwP2JGVAh3mzFOP2bZAhhyp",
            "discovery":"http://host.docker.internal:8081/realms/apisix_test_realm/.well-known/openid-configuration",
            "scope":"openid profile",
            "bearer_only":false,
            "realm":"apisix_test_realm",
            "introspection_endpoint_auth_method":"client_secret_post",
            "redirect_uri":"http://host.docker.internal:9080/",
            "session": {
               "secret": "this-should-be-a-long-random-secret-1234567890",
               "cookie": {
                "domain": "host.docker.internal",
                "path": "/",
                "samesite": "Lax",
                "secure": false
               }
            }
        }
    },
    "upstream":{
        "type":"roundrobin",
        "nodes":{
            "host.docker.internal:80":1
        }
    }
}'


This step ensures the APISIX + OIDC integration works as expected before switching to KnoxIDF. Once this is ready, you may want to clean the route:

curl -X DELETE http://127.0.0.1:9180/apisix/admin/routes/apisix-keycloak-protected



2. KnoxIDF Route Verification

  1. Federation Disabled

  2. Federation Enabled

Route creation command:

curl -X PUT 127.0.0.1:9180/apisix/admin/routes -H "X-Api-Key: edd1c9f034335f136f87ad84b625c8f1" -d '
{
    "uri":"/*",
    "id": "apisix-knoxidf-protected",
    "plugins":{
        "openid-connect":{
            "client_id":"c096d597-c7bf-4e00-ba3d-fb3820017d37",
            "client_secret":"WXpBNU5tUTFPVGN0WXpkaVppMDBaVEF3TFdKaE0yUXRabUl6T0RJd01ERTNaRE0zOjpORGc0WkRkaFlqTXRObUl4TmkwME5tWTVMVGt4WmpVdE1HUTJPV00xWVdOallqWTQ=",
            "discovery":"http://host.docker.internal:8443/gateway/knoxidf-sso/knoxidf/api/v1/.well-known/openid-configuration",
            "scope":"openid profile",
            "bearer_only":false,
            "introspection_endpoint_auth_method":"client_secret_post",
            "redirect_uri":"http://host.docker.internal:9080/",
            "token_endpoint_auth_method": "client_secret_post",
            "session": {
               "secret": "this-should-be-a-long-random-secret-1234567890",
               "cookie": {
                "domain": "host.docker.internal",
                "path": "/",
                "samesite": "Lax",
                "secure": false
               }
            }
        }
    },
    "upstream":{
        "type":"roundrobin",
        "nodes":{
            "host.docker.internal:80":1
        }
    }
}'



Key Notes



5. Future Improvements

Several advanced features commonly provided by other OIDPs are good candidates for future KnoxIDF enhancements:

Refresh Token Support check mark button

User & Client Management APIs

Role & Scope Management

Multi-OP Federation check mark button 

Additional User Parameter Provider Implementations

While the UserParamsProvider abstraction enables dynamic user claim resolution, the current implementation is limited to LDAP-based attribute lookup. Future enhancements may introduce additional provider implementations to support a wider range of enterprise identity sources.

Potential implementations include:

Supporting multiple provider types would allow KnoxIDF to serve as a more flexible identity broker across heterogeneous environments.

Automatic Federated Identity Service Enablement check mark button 

Currently, the gateway.service.knoxidf.federatedidentity.impl configuration must be explicitly set to enable federated identity persistence. This manual step introduces the possibility of misconfiguration when KnoxIDF is used across multiple topologies.

As a future improvement, Knox could automatically enable the federated identity service implementation whenever at least one topology includes the KNOXIDF service. This would simplify deployment, reduce configuration overhead, and ensure consistent federation behavior without requiring additional administrative intervention.

Automated Docker-based tests check mark button 

Knox supports automated Docker-based integration tests for a while now. It's essential that we add KnoxIDF-related tests into that framework.