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)

 {    "clientId": "product-portal",    "enabled": true,    "adminUrl": "/product-portal",    "baseUrl": "/product-portal",    "redirectUris": [        "/product-portal/*"    ],    "clientAuthenticatorType": "client-jwt",    "attributes": {        "use.jwks.url": "true",        "jwks.url": "/product-portal/k_jwks"    }}

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

 {            "clientId": "product-portal",            "enabled": true,            "adminUrl": "/product-portal",            "baseUrl": "/product-portal",            "redirectUris": [                "/product-portal/*"            ],            "clientAuthenticatorType": "client-jwt",            "attributes": {                "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
    https://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 https://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: {}->https://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: {}->https://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

janua
Les derniers articles par janua (tout voir)