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
button to start this process.
keys and certificate
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
- as the jwks_uri mechanism is in place to retrieve the new
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 Type de fichier de clés : JKS Votre fichier de clés d’accès Nom d’alias : clientkey
******************************************* |
Log
trace
The interesting lines are:
- (1) getting of
jwks_uriadminRequest
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 |
- New Keycloak online training - 19 janvier 2022
- Sizing Keycloak or Redhat SSO projects - 8 juin 2021
- Keycloak.X Distribution - 28 janvier 2021