In this article, we will share tips and tricks about understanding client Authenticator security with Keycloak

When people think about using keycloak Authorization code flow, the most straightforward to use it is to use Authoriszation Code Flow with client ID/Client secret key (using keycloak default authenticator, which is using clientID/client secret).

The Authorization code flow by itself is very secure, but it is the usage done with the secret key which can be very insecure.

client_id/client_secret security issue

When using the client key, the concern is always how to share the client key between the the client and keycloak.

If the clientid/client_secret is used by a client app running on backend server where nobody has access to the app, apart from the sysadmin who has deployed it, using keycloak client_id/client_secret is fine.

But, this point is a real concern when client secret is embedded in keycloak.json, and when anyone can decompile the application and thus having access to the client secret

The secret key can even get compromised when exchanged, as it is very easy to have someone else getting hold of it, even more, if it is exchanged by mail.

Using other Keycloak client authenticator

The 2 other alternatives not exposing clientID/client-secret with keycloak are to use specific keycloak client authenticator such as:

  • Signed JWT client authenticator
  • X509 client authentificator
Using Signed JWT client authenticator

When choosing this credential type you will have to also generate a private key and certificate for the client. The private key will be used to sign the JWT, while the certificate is used by the server to verify the signature. Click on the Generate new keys and certificate button to start this process.

client Authenticator security with Keycloak

There overall mechanism is that:

  • Client application provides a PKI (with key store)
  • client app request are signed using the private key
  • keycloak is importing the certificate public key in its external keystore
JWKS_URI

Client can also implement the a JWKS_URI.
The advantages are:

  • keycloak no longer needs import the public key, as it get automatically uploaded through JWKS_URI mechanism
  • It is possible to perform key rotation on regular basis (daily, weekly …) at client application level
    • as the jwks_uri mechanism is in place to retrieve the new public key, this has no impact on the overall keycloak infrastructure

https://www.keycloak.org/docs/latest/server_admin/index.html#_client-credentials

If you use client secured by Keycloak adapter, you can configure the JWKS URL like assuming that is the root URL of your client application. See Server Developer Guide for additional details.
Signed JWT allocator – example

Keycloak provides out-of-the box an example showcasing how to use signed JWT allocator out of the box.

Product-portal example

The product portal example is part of keycloak source distribution and the source code is available at

https://github.com/keycloak/keycloak/tree/master/examples/demo-template/product-app
Registration of the product-portal client application in keycloak

The product-portal client needs to be registered as client-jwt authenticator app.
This can be done as follows in json file format

(extract from testrealm.json)

Keycloak.json file (product-portal app)

The keycloak.json which is part of the client product-portal app indicates how this client application authenticates against Keycloak.

The 2 important points to be noticed are:

  • « clientAuthenticatorType »: « client-jwt », use.jwks.url »: « true »
  • use.jwks.url »: « true »
    • « jwks.url »: « /product-portal/k_jwks
Client-app keystore

The client app has its own keystore

keycloak-master/examples/demo-template/product-app/src/main/resources$
keytool -list -keystore keystore-client.jks -storepass storepass -v
Type de fichier de clés : JKS Fournisseur de fichier de clés : SUN
Votre fichier de clés d’accès contient 1 entrée
Nom d’alias : clientkey Date de création : 17 août 2015 Type d’entrée : PrivateKeyEntry Longueur de chaîne du certificat : 1 Certificat[1]: Propriétaire : CN=client1 Emetteur : CN=client1 Numéro de série : 14f3cb0da4f Valide du : Mon Aug 17 19:22:47 CEST 2015 au : Sun Aug 17 19:24:27 CEST 2025 Empreintes du certificat : MD5: 7A:CA:F0:5C:28:BA:C0:B0:C0:E0:AA:06:E0:3C:16:D7 SHA1 : FB:2F:D0:6A:30:1A:17:95:FE:36:43:A9:6F:02:56:97:65:43:FC:08 SHA256 : B2:CA:A4:4C:69:84:49:6F:6C:BE:47:D9:C6:67:DF:31:71:D6:22:59:BE:AF:FB:B5:AB:4F:15:43:0E:F8:18:16 Nom de l’algorithme de signature : SHA256withRSA Version : 1

******************************************* *******************************************
Log trace

The interesting lines are:

  • (1) getting of jwks_uri
    adminRequest http://localhost:8080/product-portal/k_jwks
  • (2) indicating that authentication occurred using client-jwt [org.keycloak.authentication.ClientAuthenticationFlow] (default task-12) Client product-portal authenticated by client-jwt
2019-02-13 10:14:04,730 DEBUG [org.apache.http.impl.execchain.MainClientExec] (default task-12) Proxy auth state: UNCHALLENGED
2019-02-13 10:14:04,730 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 >> GET /product-portal/k_jwks HTTP/1.1
2019-02-13 10:14:04,730 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 >> Host: localhost:8080
2019-02-13 10:14:04,730 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 >> Connection: Keep-Alive
2019-02-13 10:14:04,730 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_101)
2019-02-13 10:14:04,730 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 >> Accept-Encoding: gzip,deflate
2019-02-13 10:14:04,731 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 >> « GET /product-portal/k_jwks HTTP/1.1[\r][\n] »
2019-02-13 10:14:04,731 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 >> « Host: localhost:8080[\r][\n] »
2019-02-13 10:14:04,731 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 >> « Connection: Keep-Alive[\r][\n] »
2019-02-13 10:14:04,731 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 >> « User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_101)[\r][\n] »
2019-02-13 10:14:04,731 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 >> « Accept-Encoding: gzip,deflate[\r][\n] »
2019-02-13 10:14:04,731 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 >> « [\r][\n] »
2019-02-13 10:14:04,731 DEBUG [io.undertow.request] (default I/O-5) Matched prefix path /product-portal for path /product-portal/k_jwks
2019-02-13 10:14:04,733 DEBUG [org.keycloak.adapters.PreAuthActionsHandler] (default task-14) adminRequest http://localhost:8080/product-portal/k_jwks
2019-02-13 10:14:04,733 DEBUG [org.keycloak.adapters.KeycloakDeployment] (default task-14) resolveUrls
2019-02-13 10:14:04,734 DEBUG [org.keycloak.adapters.KeycloakDeployment] (default task-14) resolveUrls
2019-02-13 10:14:04,755 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « HTTP/1.1 200 OK[\r][\n] »
2019-02-13 10:14:04,756 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « Connection: keep-alive[\r][\n] »
2019-02-13 10:14:04,756 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « Content-Type: application/json[\r][\n] »
2019-02-13 10:14:04,756 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « Content-Length: 462[\r][\n] »
2019-02-13 10:14:04,756 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « Date: Wed, 13 Feb 2019 09:14:04 GMT[\r][\n] »
2019-02-13 10:14:04,756 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « [\r][\n] »
2019-02-13 10:14:04,756 DEBUG [org.apache.http.wire] (default task-12) http-outgoing-0 << « {« keys »:[{« kid »: »TKHSz8rxarkZ43Bc2bg265hs2bh5YvgIIaFpqOjiVgg », »kty »: »RSA », »alg »: »RS256″, »use »: »sig », »n »: »hSOOC_5Xez3o75lr3TTYun-2u0a4cF5p5Uv10UowrM7Yw-p1GYcHg-o2UN13bxHB_lefqJZ0WnJQo6cj_JcMuF1y4WlHSww0r8L0u36FKk8Uu7MOqC0-AOi2UzGIchYM5nuD3-A9g1ds2-O_ydKLKqiC6gJCKJp9b3Rs8eyJUt0_tkhTAJx-LWpCbsWHFEnU2Jbl29SS4KedYR_RdH5bNzl4L0SAHS1osWI-xIQiVYybnGVqFjJeQ9006pmOJGetNablji6TxlywP8ps9N__u3txBeKlVqzCCN1iLWQrb_NHA6GDVDBYVf-qa91358vFXRHpWpEOGftB6nZzHAzEuw », »e »: »AQAB »}]} »
2019-02-13 10:14:04,758 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 << HTTP/1.1 200 OK
2019-02-13 10:14:04,758 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 << Connection: keep-alive
2019-02-13 10:14:04,758 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 << Content-Type: application/json
2019-02-13 10:14:04,758 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 << Content-Length: 462
2019-02-13 10:14:04,758 DEBUG [org.apache.http.headers] (default task-12) http-outgoing-0 << Date: Wed, 13 Feb 2019 09:14:04 GMT
2019-02-13 10:14:04,760 DEBUG [org.apache.http.impl.execchain.MainClientExec] (default task-12) Connection can be kept alive indefinitely
2019-02-13 10:14:04,762 DEBUG [org.apache.http.impl.conn.PoolingHttpClientConnectionManager] (default task-12) Connection [id: 0][route: {}->http://localhost:8080] can be kept alive indefinitely
2019-02-13 10:14:04,762 DEBUG [org.apache.http.impl.conn.PoolingHttpClientConnectionManager] (default task-12) Connection released: [id: 0][route: {}->http://localhost:8080][total kept alive: 1; route allocated: 1 of 64; total allocated: 1 of 128]
2019-02-13 10:14:04,793 DEBUG [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider] (default task-12) Public keys retrieved successfully for model demo::client::8241edf2-d120-4322-85ee-37774dbd50f6. New kids: [TKHSz8rxarkZ43Bc2bg265hs2bh5YvgIIaFpqOjiVgg]
2019-02-13 10:14:04,795 DEBUG [org.keycloak.models.sessions.infinispan.InfinispanSingleUseTokenStoreProviderFactory] (default task-12) Not having remote stores. Using normal cache ‘actionTokens’ for single-use cache of token
2019-02-13 10:14:04,798 DEBUG [org.keycloak.authentication.ClientAuthenticationFlow] (default task-12) client authenticator SUCCESS: client-jwt
2019-02-13 10:14:04,798 DEBUG [org.keycloak.authentication.ClientAuthenticationFlow] (default task-12) Client product-portal authenticated by client-jwt
2019-0

Olivier Rivat

Senior Software Engineer with over 25 years of experience doing Software Development, Support and Consulting in Identity and Access Management Solutions.
Specialised in IAM (security, access control, identity management) and Open Source integration, settled in 2004 by IAM industry veteran, JANUA offers high value-added products and services to businesses and governements with a concern for Identity Management and Open Source components.
JANUA provides better security, build relationships, and enable new cloud, mobile, and IoT offerings from any device or connected thing.
Olivier Rivat