Configure OpenID Connect with cert-bound access tokens

Uses: Kong Gateway deck
TL;DR

Certificate-bound access tokens allow binding tokens to clients. This guarantees the authenticity of the token by verifying whether the sender is authorized to use the token for accessing protected resources.

You can configure certificate-bound access token authentication with OpenID Connect by mounting your certificates into an IdP, like Keycloak, and configuring the IdP with a client and mTLS authentication. Then, configure the TLS Handshake Modifier plugin with config.tls_client_certificate set to REQUEST and the OIDC plugin with your IdP issuer, config.proof_of_possession_mtls set to strict, and enable config.proof_of_possession_auth_methods_validation. Generate an access token and pass it in a request.

Prerequisites

This is a Konnect tutorial and requires a Konnect personal access token.

  1. Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.

  2. Export your token to an environment variable:

     export KONNECT_TOKEN='YOUR_KONNECT_PAT'
    
  3. Run the quickstart script to automatically provision a Control Plane and Data Plane, and configure your environment:

     curl -Ls https://get.konghq.com/quickstart | bash -s -- -k $KONNECT_TOKEN --deck-output
    

    This sets up a Konnect Control Plane named quickstart, provisions a local Data Plane, and prints out the following environment variable exports:

     export DECK_KONNECT_TOKEN=$KONNECT_TOKEN
     export DECK_KONNECT_CONTROL_PLANE_NAME=quickstart
     export KONNECT_CONTROL_PLANE_URL=https://us.api.konghq.com
     export KONNECT_PROXY_URL='http://localhost:8000'
    

    Copy and paste these into your terminal to configure your session.

This tutorial requires Kong Gateway Enterprise. If you don’t have Kong Gateway set up yet, you can use the quickstart script with an enterprise license to get an instance of Kong Gateway running almost instantly.

  1. Export your license to an environment variable:

     export KONG_LICENSE_DATA='LICENSE-CONTENTS-GO-HERE'
    
  2. Run the quickstart script:

    curl -Ls https://get.konghq.com/quickstart | bash -s -- -e KONG_LICENSE_DATA 
    

    Once Kong Gateway is ready, you will see the following message:

     Kong Gateway Ready
    

decK is a CLI tool for managing Kong Gateway declaratively with state files. To complete this tutorial, install decK version 1.43 or later.

This guide uses deck gateway apply, which directly applies entity configuration to your Gateway instance. We recommend upgrading your decK installation to take advantage of this tool.

You can check your current decK version with deck version.

For this tutorial, you’ll need Kong Gateway entities, like Gateway Services and Routes, pre-configured. These entities are essential for Kong Gateway to function but installing them isn’t the focus of this guide. Follow these steps to pre-configure them:

  1. Run the following command:

    echo '
    _format_version: "3.0"
    services:
      - name: example-service
        url: http://httpbin.konghq.com/anything
    routes:
      - name: example-route
        paths:
        - "/anything"
        service:
          name: example-service
    ' | deck gateway apply -
    

To learn more about entities, you can read our entities documentation.

In this tutorial, you’ll need a DNS hostname that you can use for your Keycloak server. You’ll need to replace the hostname in any commands in this tutorial with your own hostname.

To complete this tutorial, you need Java 11.0.12 or later installed.

Java is required because we’re using keytool to create the CA JKS keystore with your root certificates.

Generate certificates

In this tutorial, you’ll need various certificates such as:

  • CA certificate store
  • Client certificate
  • Server certificate
  1. Make an /oidc/certs directory to store the certificates and run the following steps from that directory:
    mkdir -p ~/oidc/certs && cd ~/oidc/certs
    
  2. Run the following to help you generate a CA certificate:

    openssl genrsa -out rootCA.key 4096
       
    openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 \
      -out rootCA.crt \
      -subj "/C=US/ST=State/L=City/O=Organization/OU=Department/CN=Root CA"
       
    echo "Root CA certificate (rootCA.crt) generated successfully."
    
  3. Export the passwords you want to use for your PKCS#12 and keystore. Both must be at least six characters:
    export PKCS12_PASSWORD='YOUR-PASSWORD'
    export KEYSTORE_PASS='YOUR-PASSWORD'
    
  4. Generate the client certificate:

    openssl genrsa -out client.key 2048
    openssl req -new -key client.key -out client.csr \
      -subj "/C=US/ST=State/L=City/O=ClientOrg/OU=Dev/CN=client-app"
       
    cat > client.ext <<EOF
    basicConstraints=CA:FALSE
    keyUsage = digitalSignature, keyEncipherment
    extendedKeyUsage = clientAuth
    EOF
       
    openssl x509 -req \
      -in client.csr \
      -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
      -out client.crt -days 365 -sha256 -extfile client.ext
    
  5. Generate the server certificate:

    Important: In this tutorial, use your DNS hostname in place of your.hostname.

    openssl genrsa -out keycloak.key 2048
       
    openssl req -new -key keycloak.key -out keycloak.csr \
      -subj "/C=US/ST=State/L=City/O=ClientOrg/OU=Dev/CN=your.hostname"
       
    cat > keycloak.ext <<EOF
    authorityKeyIdentifier=keyid,issuer
    basicConstraints=CA:FALSE
    keyUsage = digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth
    subjectAltName = @alt_names
       
    [alt_names]
    DNS.1 = your.hostname
    EOF
       
    openssl x509 -req \
      -in keycloak.csr \
      -CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
      -out keycloak.crt -days 365 -sha256 -extfile keycloak.ext
    

    This is used to authenticate with Keycloak and to consume the API with access token. The generated CN must adhere to the pre-defined pattern for Keycloak validation.

  6. Build a PKCS#12 keystore file:
    openssl pkcs12 -export \
      -in keycloak.crt \
      -inkey keycloak.key \
      -certfile rootCA.crt \
      -out keycloak-keystore.p12 \
      -name keycloak \
      -passout pass:$PKCS12_PASSWORD
    
  7. Import the root certificates to the .p12 file:

    keytool -importkeystore \
      -deststorepass $KEYSTORE_PASS \
      -destkeypass $KEYSTORE_PASS \
      -destkeystore keycloak-keystore.jks \
      -srckeystore keycloak-keystore.p12 \
      -srcstoretype PKCS12 \
      -srcstorepass $PKCS12_PASSWORD \
      -alias keycloak
    
  8. Configure Keycloak to trust certificates signed by the CA:

    keytool -import -alias rootca \
      -keystore keycloak-truststore.p12 \
      -storetype PKCS12 \
      -file rootCA.crt \
      -storepass "$PKCS12_PASSWORD"
       
    keytool -list -keystore keycloak-truststore.p12 -storepass "$PKCS12_PASSWORD"
    

    Type y when prompted to trust the certificate. The Keycloak server presents this certificate to the client.

Configure Keycloak

Important: You must run the following in a new terminal window because Keycloak’s container is started in the foreground.

  1. In a new terminal window, export your trust store password and hostname:
    export PKCS12_PASSWORD='YOUR-PASSWORD'
    export HOSTNAME='YOUR-KEYCLOAK-HOSTNAME'
    
  2. Then, start Keycloak in Docker:

    Important: In this tutorial, use your DNS hostname in place of your.hostname.

    docker run \
      -p 9443:9443 \
      --network kong-quickstart-net \
      -v $(pwd)/oidc/certs:/opt/keycloak/ssl \
      -e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
      -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
      --name keycloak \
      quay.io/keycloak/keycloak \
      start \
      --https-port=9443 \
      --https-certificate-file=/opt/keycloak/ssl/keycloak.crt \
      --https-certificate-key-file=/opt/keycloak/ssl/keycloak.key \
      --https-trust-store-file=/opt/keycloak/ssl/keycloak-truststore.p12 \
      --https-trust-store-password=$PKCS12_PASSWORD \
      --https-client-auth=request \
      --hostname=your.hostname
    
  3. Open the Keycloak admin console. The default URL of the console is https://your.hostname:9443/admin/master/console/.
  4. In the sidebar, open Clients, then click Create client.
  5. Configure the client:

    Section

    Settings

    General settings
    • Client type: OpenID Connect
    • Client ID: any unique name, for example kong
    Capability config
    • Enable Client authentication
    • Enable Authorization
    • Select Standard flow, Direct access grants, and Service accounts roles
  6. Click the Credentials tab.
  7. Select “X509 Certificate” from the Client Authenticator dropdown menu.
  8. Enter CN=client-app, OU=Dev, O=ClientOrg, L=City, ST=State, C=US in the Subject DN field.
  9. Click Save and agree to the change.
  10. Click the Advanced tab.
  11. In Advanced settings, enable OAuth 2.0 Mutual TLS Certificate Bound Access Tokens Enabled.
  12. Click Save at the bottom of the Advanced settings section.
  13. Export your issuer:
    export DECK_ISSUER='https://your.hostname:9443/realms/master'
    

    Important: In this tutorial, use your DNS hostname in place of your.hostname.

Enable TLS handshake plugin

Configure the TLS Handshake Modifier plugin to request that the client to send a client certificate:

echo '
_format_version: "3.0"
plugins:
  - name: tls-handshake-modifier
    route: example-route
    config:
      tls_client_certificate: REQUEST
' | deck gateway apply -

Alternatively, you can use the Mutual TLS Authentication plugin instead.

Enable the OpenID Connect plugin

Using the Keycloak and Kong Gateway configuration from the prerequisites, set up an instance of the OpenID Connect plugin.

Enable the OpenID Connect plugin on the example-service Service:

echo '
_format_version: "3.0"
plugins:
  - name: openid-connect
    route: example-route
    config:
      issuer: "${{ env "DECK_ISSUER" }}"
      auth_methods:
      - bearer
      proof_of_possession_mtls: strict
      proof_of_possession_auth_methods_validation: true
' | deck gateway apply -

In this example:

  • issuer: Settings that connect the plugin to your IdP (in this case, the sample Keycloak app).
  • proof_of_possession_mtls: By setting this to strict, it ensures all tokens are verified.
  • proof_of_possession_auth_methods_validation: Ensures that only the auth_methods that are compatible with Proof of Possession (PoP) can be configured when PoP is enabled.

Generate the access token

Now, you can generate your access token to authenticate with certificate-bound authentication.

Navigate to the /oidc/certs you created previously and generate the access token:

curl -s --location --request POST 'https://your.hostname:9443/realms/master/protocol/openid-connect/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=kong' \
  --data-urlencode 'grant_type=client_credentials' \
  --cert client.crt \
  --key client.key \
  --cacert rootCA.crt | jq -r .access_token

Important: In this tutorial, use your DNS hostname in place of your.hostname.

Export the access token:

export ACCESS_TOKEN='YOUR-ACCESS-TOKEN'

The access token, by default, expires in 60 seconds. If you want to extend the expiration, you can configure this in the Keycloak Advanced settings for the client by adjusting the Access Token Lifespan settings.

Validate the OpenID Connect plugin configuration

Request the Service with the Keycloak access token:

curl -isk \
  -X GET "https://localhost:8443/anything" \
  -H "Authorization:Bearer $ACCESS_TOKEN" \
  --cert client.crt \
  --key client.key

You should get an HTTP 200 response.

Cleanup

If you created a new control plane and want to conserve your free trial credits or avoid unnecessary charges, delete the new control plane used in this tutorial.

curl -Ls https://get.konghq.com/quickstart | bash -s -- -d
Something wrong?

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!