Overview
One of the main vulnerabilities of OAuth are bearer tokens. With OAuth, presenting a valid bearer token is enough proof to access a resource. This can create problems since the client presenting the token isn’t validated as the legitimate user that the token was issued to.
For an alternative solution, please refer to: Demonstrating Proof-of-Possession.
Certificate-bound access tokens can solve this problem by binding tokens to clients. This ensures the legitimacy of the token because the it requires proof that the sender is authorized to use a particular token to access protected resources.
To enable certificate-bound access for OpenID Connect, you must ensure that the auth server is set up to generate OAuth 2.0 Mutual TLS certificate-bound access tokens.
If you are configuring this in Keycloak, see the Keycloak configuration section in the prerequisites. For alternative auth servers, consult their documentation to configure this functionality.
Some of the instructions in the other how-to guides for OpenID Connect support validation of access tokens using mTLS proof of possession.
Enabling the proof_of_possession_mtls
configuration option in the plugin helps to ensure that the supplied access token
belongs to the client by verifying its binding with the client certificate provided in the request.
The certificate-bound access tokens are supported by the following auth methods:
- JWT Access Token Authentication
- Introspection Authentication
-
Session Authentication is only compatible with certificate-bound access tokens when used along with one of the other supported authentication methods:
- When the configuration option
proof_of_possession_auth_methods_validation
is set tofalse
and other non-compatible methods are enabled, if a valid session is found, the proof of possession validation will only be performed if the session was originally created using one of the compatible methods. - If multiple
openid-connect
plugins are configured with thesession
auth method, we strongly recommend configuring different values ofsession_secret
across plugin instances for additional security. This avoids sessions being shared across plugins and possibly bypassing the proof of possession validation.
- When the configuration option
The following example shows how to enable this feature for the JWT Access Token Authentication method. Similar steps can be followed for the other methods.
Demo
Prerequisites
Follow these prerequisites to set up a demo Keycloak app and a Kong service and route for testing mTLS client auth.
In most cases, the OpenID Connect plugin relies on a third party identity provider (IdP). The examples in this guide use Keycloak as a sample IdP.
Expand the following sections to configure Keycloak and Kong Gateway.
Configure Keycloak
All the *.test
domains in the following examples point to the localhost
(127.0.0.1
and/or ::1
).
We use Keycloak as the identity provider in the following examples, but the steps will be similar in other standard identity providers. If you encounter difficulties during this phase, refer to the Keycloak documentation.
- Create a confidential client
kong
withprivate_key_jwt
authentication and configure Keycloak to download the public keys from [the OpenID Connect Plugin JWKS endpoint][json-web-key-set]:
-
Create another confidential client
service
withclient_secret_basic
authentication. For this client, Keycloak will auto-generate a secret similar to the following:cf4c655a-0622-4ce6-a0de-d3353ef0b714
. Enable the client credentials grant for the client:
-
(Optional) Create another confidential client
cert-bound
with settings similar to theservice
client created previously. From the Advanced tab, enable the OAuth 2.0 Mutual TLS Certificate Bound Access Tokens Enabled toggle. -
(Optional, to test mTLS Client Authentication) Create another confidential client
client-tls-auth
with settings similar to theservice
client created above. From the Credentials tab, select the X509 Certificate Client Authenticator and fill the Subject DN field so that it matches the Kong client certificate’s, e.g.:CN=JohnDoe, OU=IT
. -
(Optional, to test Demonstrating Proof-of-Possession Client Authentication) Create another confidential client
client-dpop-auth
with settings similar to theservice
client created above. From the Advanced tab, enable theOAuth 2.0 DPoP Bound Access Tokens Enabled toggle. - Create a verified user with the name:
john
and the non-temporary password:doe
that can be used with the password grant:
Alternatively you can download the exported Keycloak configuration, and use it to configure the Keycloak. Please refer to Keycloak import documentation for more information.
You need to modify Keycloak standalone.xml
configuration file, and change the socket binding from:
<socket-binding name="https" port="${jboss.https.port:8443}"/>
to
<socket-binding name="https" port="${jboss.https.port:8440}"/>
The Keycloak default https
port conflicts with the default Kong TLS proxy port,
and that can be a problem if both are started on the same host.
Note: The mTLS Client Authentication, along with the proof of possession feature that validates OAuth 2.0 Mutual TLS Certificate Bound Access Tokens, both require configuring Keycloak to validate client certificates with mTLS using the
--https-client-auth=request
option, and to configure TLS appropriately, including adding the trusted client certificates to the truststore. For more information, refer to the Keycloak documentation.
Configure Kong Gateway
-
Create a service:
curl -i -X POST http://localhost:8001/services \ --data "name=openid-connect" \ --data "url=https://httpbin.konghq.com/anything"
-
Create a route:
curl -i -X POST http://localhost:8001/services/openid-connect/routes \ --data "name=openid-connect" \ --data "paths[]=/"
Configure certificate-bound access tokens
-
Configure Kong Gateway to use mTLS client certificate authentication. You can do this by configuring the TLS Handshake Modifier plugin or the Mutual TLS Authentication plugin:
curl -X POST http://localhost:8001/plugins \ --data "name=tls-handshake-modifier" \ --data "service.name=openid-connect"
If this is configured correctly, it returns a
200
response with the following data:{ "id": "a7f676e6-580d-4841-80de-de46e1f79eb2", "name": "tls-handshake-modifier", "service": { "id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd" } }
-
To enable certificate-bound access tokens, use the
proof_of_possession_mtls
configuration option:curl -X PUT http://localhost:8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \ --data "name=openid-connect" \ --data "service.name=openid-connect" \ --data "config.issuer=https://keycloak.test:8440/auth/realms/master" \ --data "config.client_id=cert-bound" \ --data "config.client_secret=cf4c655a-0622-4ce6-a0de-d3353ef0b714" \ --data "config.auth_methods=bearer" \ --data "config.proof_of_possession_mtls=strict"
If this is configured correctly, it returns a
200
response with the following data:{ "id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9", "name": "openid-connect", "service": { "id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd" }, "config": { "issuer": "https://keycloak.test:8440/auth/realms/master", "client_id": [ "cert-bound" ], "client_secret": [ "cf4c655a-0622-4ce6-a0de-d3353ef0b714" ], "auth_methods": [ "bearer" ], "proof_of_possession_mtls": "strict", } }
- Obtain the token from the IdP, making sure to modify the following command for your environment:
curl -f -X POST https://keycloak.test:8440/auth/realms/master/protocol/openid-connect/token \ --cert client-cert.pem \ --cert-key client-key.pem \ --data "client_id=cert-bound" \ --data "client_secret=cf4c655a-0622-4ce6-a0de-d3353ef0b714" \ --data "grant_type=client_credentials" \
If this is configured correctly, it returns a
200
response with the following data:{ "access_token": "eyJhbG...", }
The token you obtain should include a claim that consists of the hash of the client certificate:
{ "exp": 1622556713, "typ": "Bearer", "cnf": { "x5t#S256": "hh_XBS..." } }
- Access the service using the same client certificate and key used to obtain the token:
curl -f -X POST https://kong.test:8443 \ -H "Authorization: Bearer eyJhbGc..." \ --cert client-cert.pem \ --cert-key client-key.pem \
If this is configured correctly, it returns a
200
response:HTTP/1.1 200 OK