I'm trying to get the key from Keycloak open-id connect certs endpoint that allow me to validate a JWT token. The api to fetch the keys seam to work :
GET http://localhost:8080/auth/realms/my-realm/protocol/openid-connect/certs
{
"keys": [
{
"kid": "MfFp7IWWRkFW3Yvhb1eVrtyQQNYqk6BG-6HZFpl_JxI",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "qDWXUhNtfuHNh0lm3o-oTnP5S8ENpzsyi-dGrjSeewxV6GNiKTW5INJ4hDQ7ZWkUFfJJhfhQWJofqgN9rUBQgbRxXuUvEkrzXQiT9AT_8r-2XLMwRV3eV_t-WRIJhVWsm9CHS2gzbqbNP8HFoB_ZaEt2FYegQSoAFC1EXMioarQbFs7wFNEs1sn1di2xAjoy0rFrqf_UcYFNPlUhu7FiyhRrnoctAuQepV3B9_YQpFVoiUqa_p5THcDMaUIFXZmGXNftf1zlepbscaeoCqtiWTZLQHNuYKG4haFuJE4t19YhAZkPiqnatOUJv5ummc6i6CD69Mm9xAzYyMQUEvJuFw",
"e": "AQAB"
}
]
}
but where is the key and how to decode it ?
$.keys[0].n does not look like base64 and I cannot figure out what it is ?
...if someone can tell me how to get the public key from that payload it will be great !
Looking at https://github.com/keycloak/keycloak/blob/master/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java it seams that returned key are pem encoded using :
modulus
exponent
Look at the mentionned java class to get a public key in java or https://github.com/tracker1/node-rsa-pem-from-mod-exp to get the public key in javascript.
Type of the key (or keys) is JSON Web Key (JWK). List of supported library is on OpenID web page. I am using jose.4.j for retrieve keys from Keycloak.
Related
I configured Hydra to return JWT in access_token (STRATEGIES_ACCESS_TOKEN=jwt). Pasting the resulting token[1] in jwt.io works—it's decoded successfully and the data looks right. Now I want to verify the signature.
So I open http://localhost:9000/.well-known/jwks.json and extract the public RSA keys[2][3], but none of them works—jwt.io gives me "Invalid Signature".
FYI the same error is also thrown by FusionAuth JWT library. My Scala code to get the keys:
private val hydraVerifiers = {
val keys = retrieveKeysFromJWKS("http://localhost:9000/.well-known/jwks.json")
keys.asScala.map(JSONWebKey.parse).map(_.asInstanceOf[RSAPublicKey]).map(RSAVerifier.newVerifier)
}.toSeq
(If you're not familiar with Scala/FusionAuth, the code above retrieves keys from a URL, parses them to java.security.PublicKey object, and creates JWT verifiers based on the public keys)
Any suggestions?
[1]
eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpjMzQ5NWJkMC0wZDFhLTRiOWMtOWZhNy1hZjE5ZWNlODMxMTEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOltdLCJjbGllbnRfaWQiOiJpZC5ldmVyeS5leHQuc3RlYW0iLCJleHAiOjE2Mjc3NTI0ODEsImV4dCI6e30sImlhdCI6MTYyNzc0ODg4MCwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo5MDAwLyIsImp0aSI6IjAxNjRkMzk4LTY4OWQtNDUzZi05YmMxLWVjYTgyMGJkOWY1OSIsIm5iZiI6MTYyNzc0ODg4MCwic2NwIjpbImVpZC5leHRlbmQiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiIweDMyY2ZkMSJ9.h2XUovsF7e5OI60BnKakbHVQJoNJmxQSsmLMy2MxyxFHQ4VMDdISPbkJVD5fsPCdLVw3RcggSid8S7fHJpDWqgJD0UP1WZBqlYN6E2FYs25QRsn8tvOlb9RqDAq1sqhSA1DhPCSsg0OB37gqQq4M8TjYfH0gqrj6oiU9YvgvlKU2Q7uaXfUjxWNkW_6KCZsdUSAGsHtIT18BWudcry7xafVsbrSdp7HkdRhbbdGCh9-q7lTWx6dJB5gOUTI4TYWCzOZnos48NilIhrk_sZ6V7qk6cxkmGVQflQZ9sLmHGewY6IF7j5OKBDcl-fNGfOyvhn2HkTGzTb_H1uB4dxm1NkopPf4dQHU4A63CnoJLqS_Rhg7aYvdcSyJTtiEGXOQjApP9QkawePsd7JYlSHE7XvYxW7frM0VVO04Fu8HjftMliYKXHct0w9bVeykwRGF_m63VGaain0DjE6H5UKLNdNQvg5r4Azu-hBiR5sH5cL6j26ffEx_P-XnIK5cHyX2A9GHk9V_p6_SiLtKpEuKO4sZPBU4zc6z0J1_aFn-AAtFRpKvHKAfUNCnVacEkXO4k6EfJ3qLkrF2KNIfYCqcTTFl8tPdmbb_n020xdCZNpVKPW_G9qRqpf7okwx9ppGO6OD__uIXNEOcuKD-9pVk2qsdl4RqHAvVO6GNurbcAXQU
[2]
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx33RsetBbPDnYSTJh6Y8esAxiqkWV5pawNUXoDvqZ46lPm1B2tTC0JDlpF5YmuoMEPIqFJJ5BiJpo8CN+5CGDnxSFfiamRomCa2ij6s+XmcPAC41POwpQn0+ijHd/wjh/lEFl+w6xhNhDoLTjxDYIDS8OQ5EhYH8vnHrNnOeh3X7f9kLC00M1S8f0uxbEzjqsfRjSUghIAn8NB3u/wj5/1G++guXDv8WRjadAWjzIhUy9MERIdhK6Mitp9xwPyXLFCQViYLeq9gTGYUqh4G9LzHPAlJW9qWwNM5ij3K+X4Qeu9jJaHCL2ZPywkSkoa6veDjnFs7uS695FqM+5NGyEWPeriCJ1zkL2n8PiGRDSk9jFlQi93+BFyIfUkTPD32CfLV5dYs+a3vV5Uy3cxC+JgxnqVlXY7k3MoaxU08oOMOuGNdCG9RoTtWuERtLa4wkGCKwfdnMzwhWQzKhQGPGRUTq+CxI4WCghfBfqBef74bldmmH/t42FfuCjWvVi6PlDi0YJtixrayJeVBy89dp0Xh1O+g4S9KRx5O1hiJPSNz6Q5O8lHSp4naQhsCGA53JoFofZSj9nBrL6o/Vlh0RPX0c9I2rpPr8YPGC3pxPvbMeP34hbW15F6JSSA9gACc+A2RYWhgZbR2QojJLhhQggSaWZ0g9TF+XG8WQsLISHDUCAwEAAQ==
[3]
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+Xpe52O+qFGT4WHoJ2bsIC7/Vb51RulDKx/kNNchoERh1pg4TRL2WjXW7CK72CMz7cpNfsuxeC0RXLRMU+aAXyrsWkVm9u0IZsruOHOFjOeWFlqI5ZdnnkHYaH3GQhfXUcnJFQGHpzTn1hWlGT2UWuuSxqYcQ/SUeD7r5rvzOSAPAztLTAzsMmMrDPRz9KWDpmhLPh32WE903eooLAYsneNkGBvaWbc3PF+39P+5z730pWjgaeDsFHJug9c34AxmWP3NfudguW6/Ud9GR250iBHqPBcInafry9orWb+qndWXZLv74gCxovxdA0xuJnO+P/KeYZ5fCI4Lv8VT8H8ascy4LenUjyoR9dUsLpvOyYjNg7qPfZFyi0fN/feyh5M4MAkqPi3MmLzYWI9vlzmUBSzJ8TxHoonBHYIHwjif3ovRavmDSsuEQ2iZcz7P27hx569B50a66VUcXJVxoxSOgsjBD/WG6d97ACftZUaZxYk1SUrp5KGL1qoOlKGTDuIlJjP87/0Rg2TBMR8a6EdofSpAVC1RvB1AXupMK6xfMCRJzMAv4O54Y7esCT6u1hE/z9toQ5ArUPTg+rdskXg9eEQUaIykjJaxaFtzayCyhWFHBqdusZIDcU5e8lRaCzBXfMiXrlGj1E0wviynwfVf+Q8X/HZBXhEtlCfOm6ESe8CAwEAAQ==
UPDATE:
After further testing it seems as though the RSA setup I thought was working, actually isn't. Until CAS can support asymmetric keys for JWT tickets, this question is rendered irrelevant.
My use case:
CAS VERSION: 6.2.0-RC2
Using CAS for single sign on for a number of applications. The backend identity provider is LDAP. The client of interest is an SPA that redirects to CAS for login. Upon successful login to CAS, a JWT is issued via a configured service provider. I have setup the service provider to sign the JWT using asymmetric keys via RSA. This is all working. What I can't get to work is the "jwtTicketSigningPublicKey" actuator endpoint.
I want to be able to publish the public key so that my SPA is able to dynamically grab the public key for signage validation so that I can rotate the RSA keys if necessary without having to change anything on the SPA side. I assumed this was the purpose for this feature, but when I hit the endpoint after exposing it as directed here, I get a 404.
My config:
Here is what my cas.config file looks like as it relates to this endpoint:
# Expose it
management.endpoints.web.exposure.include=jwtTicketSigningPublicKey
# Enable it
management.endpoint.jwtTicketSigningPublicKey.enabled=true
# Allow access to it
cas.monitor.endpoints.endpoint.jwtTicketSigningPublicKey.access=ANONYMOUS
I then bounce the CAS server and I can see the endpoint in the actuator links at http://mycas.com/cas/actuator like so:
"jwtTicketSigningPublicKey":{"href":"http://mycas.com/cas/actuator/jwtTicketSigningPublicKey","templated":false}
As the document refers to, I can pass an optional service parameter like so to get the public key associated to a "per-service" implementation, which is what I have. I hit the endpoint like so:
http://mycas.com/cas/actuator/jwtTicketSigningPublicKey?service=http://example.org
At which point I receive a 404. I also get a 404 if I hit the endpoint without the service parameter. But I would expect that since I don't actually have a globally defined RSA pair.
My attempt at a solution:
The most logical place I can imagine this public key should be provided is in the service configuration along with where I am providing the private key. However I can find no documented parameter by which to define the public key. This is what I have tried to no avail.
{
"#class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^http://.*",
"name" : "Sample",
"id" : 10,
"properties" : {
"#class" : "java.util.HashMap",
"jwtAsServiceTicket" : {
"#class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty",
"values" : [ "java.util.HashSet", [ "true" ] ]
},
"jwtSigningSecretAlg" : {
"#class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty",
"values" : [ "java.util.HashSet", [ "RS256" ] ]
},
"jwtAsServiceTicketSigningKey" : {
"#class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty",
"values" : [ "java.util.HashSet", [ "MyPrivateKeyGoesHere" ] ]
},
"jwtAsServiceTicketSigningPublicKey" : {
"#class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty",
"values" : [ "java.util.HashSet", [ "MyPublicKeyGoesHere" ] ]
}
}
}
The signing key works and is a documented parameter. Also, the signing secret algorithm is documented here. But the last "...SigningPublicKey" parameter was a complete shot in the dark because I have not found any docs on the matter other than what is defined here.
Summary:
So what I am hoping to find by this question, is someone that is familiar with this endpoint and how to configure it properly in order to make the signing public key available to my SPA.
I realized there are many iterations of this questions. But I can't seem to understand the answer correctly.
We have secured our rabbitmq and rest endpoints with a oauth2 spring server similar to this post. But it doesn't have all of the features we need and want. So we would like to use Keycloak. I have been successful with securing the rest endpoint by just going to the new version of spring security 5.1 and specifing the security.oauth2.resource.jwk.key-set-uri and setting the necessary dependencies and configuration.
While trying to secure the RabbitMQ, I have been running into problems checking the bearer token from the message header because the keycloak jwks endpoint isn't returning the true RSA public key.
RabbitMQ uses the CustomMessageListenerContainer to get the token from the message header and uses the DefaultTokenServices to check the token.
From my understanding, the endpoint that responds with the key is https://keycloak-server/auth/realms/my-realm/protocol/openid-connect/certs
Doing a HttpGet on this endpoint, I get a response that looks like the following
{
"keys": [{
"kid": "7JUbcl_96GNk2zNh4MAORuEz3YBuprXilmTXjm0gmRE",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "nE9gEtzZvV_XisnAY8Hung399hwBM_eykZ9J57euboEsKra8JvDmE6w7SSrk-aTVjdNpjdzOyrFd4V7tFqev1vVJu8MJGIyQlbPv07MTsgYE5EPM4DxdQ7H6_f3vQjq0hznkFvC-hyCqUhxPTXM5NgvH86OekL2C170xnd50RLWw8FbrprP2oRjgBnXMAif1Dd8kwbKKgf5m3Ou0yTVGfsCRG1_LSj6gIEFglxNHvGz0RejoQql0rGMxcW3MzCvc-inF3FCafQTrG5eWHqp5xXEeMHz0JosQ7BcT8MVp9lHT_utiazhQ1uKZEb4uoYOyy6mDDkx-wExpZkOx76bk_Yu-N25ljY18hNllnV_8gVMkX46_vcc-eN3DRZGNJ-Asd_sZrjbXbAvBbKwVxZeOTaXiUdvl8O0G5xX2xPnS_WA_1U4b_V1t28WtnX4bqGlOejW2kkjLvNrpfQ5fnvLjkl9I2B16Mbh9nS0LJD0RR-AkBsv3rKEnMyEkW9UsfgYKLFKuH32x_CXi9uyvNDas_q8WS3QvYwAGEMRO_4uICDAqupCVb1Jcs9dvd1w-tUfj5MQOXB-srnQYf5DbFENTNM1PK390dIjdLJh4k2efCJ21I1kYw2Qr9lHI4X2peTinViaoOykykJiol6LMujUcfqaZ1qPKDy_UnpAwGg9NyFU",
"e": "AQAB"
}
]
}
From my understanding, the field with key "n" is supposed to be an RSA256 key. Adding it to a RSAVerifier eventually gets an error of "Caused by: org.springframework.security.jwt.codec.InvalidBase64CharacterException: Bad Base64 input character decimal 95 in array position 2."
However, if I login to keycloak admin page and go into the realm settings-> keys and click the public key, a popup shows the public key minus the "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----" headers and footers. Hard coding this enables everything to work.
Is the key encoded?
I've tried doing a Base64Utils.decodeFromUrlSafeString and a Base64Utils.decodeFromString. The first returning something smaller and doesn't lool like the key and the later creating an Illegal argument exception Illegal base64 character 5f.
Update:
The n being returned is the modulous and e is the public exponent of the public key. But how does one get the actual key string?
The keys are also directly on https://keycloak-server/auth/realms/my-realm, in a format directly exploitable with your code:
{
"realm": "my-realm",
"public_key": "MIIBI...",
"token-service": "https://keycloak-server/auth/realms/my-realm/protocol/openid-connect",
"account-service": "https://keycloak-server/auth/realms/my-realm/account",
"tokens-not-before": 0
}
I'll found it also on:
open admin console
choose realm
choose Realm Settings
open tab 'Keys'
open tab 'active'
in the column 'Public keys' press 'Public Key
a popup with the public key appears.
There is toIntegerBytes before base64 encode, so it is not just base64 decode. Try:
BigInteger modulus = new BigInteger(1, Base64.decodeBase64("n-value-here"));
BigInteger exponent = new BigInteger(1, Base64.decodeBase64("e-value-here"));
JWKS endpoints are designed to have their keys changed over time (in a process called key rotation), so retrieving the public key as per the accepted answer is not a good idea. What you should opt for instead is to use a JWKS client. I use node-jwks-rsa, but the same creators also have a java implementation (jwks-rsa-java).
I'm writing backend microservice that receives requests from front-end which have Authorisation: Bearer ... header, with token obtained from keycloak (which is inside docker container).
I got the RSA public key to verify the signature of that token from Keys section of realm settings, but it seems that when container with keycloak restarts, it regenerates pair of keys, and my public key set in service config becomes invalid.
What is the proper way to work with RSA public key from keycloak? Is there some way to configure it to use a fixed pair of keys for realm? Are keys exported when realm exports? Or I have to get the public key from keycloak using url like http://keycloak:8080/auth/realms/:realm_name:, which I rather not to do because this adds a dependency between keycloak and backend.
You should verify the JWT token's signature based on the issuer identity server's /.well-known/jwks endpoint.
1) Query the issuer identity server's /.well-known/jwks endpoint (JWKS stands for JSON Web Key Set)
2) From the JWKS, get the JWK (JSON Web Key) with the same kid (Key ID) as the Bearer token we are verifying. To get the kid from your JWT token, first decode it using jwt.io's Debugger tool.
3) As long as identity server-issued tokens are verified with an asymmetric cryptography algorithm (e.g.: RS256), we can verify the signature with the Public Key only (so you won't need the Private Key)
4) The Public Key can be retrieved from the JWK (it is the x5c entry in the JWK JSON )
5) Verify the JWT Bearer token's signature with this Public Key.
For example, in Java you can verify it like this:
// verify JWT signature based on Access Identity's JWKS RSA public key (RS256)
try {
Jwk jwk = new UrlJwkProvider(new URL(issuer + Constants.JWKS_ENDPOINT)).get(decodedJWT.getKeyId());
final PublicKey publicKey = jwk.getPublicKey();
if (!(publicKey instanceof RSAPublicKey)) {
throw new IllegalArgumentException("Key with ID " + decodedJWT.getKeyId() + " was found in JWKS but is not a RSA-key.");
}
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(issuer)
.build(); //Reusable verifier instance
verifier.verify(bearerToken);
LOGGER.info("Token verified!");
} catch (Exception e) {
LOGGER.error(e.getMessage());
throw new InvalidAccessTokenException("JWTVerificationException - Invalid token signature.");
}
I'm essentially doing something very similar to this post. I would like to be able to verify some JWT tokens that have been signed on an IdentityServer, and there are a few details that I haven't figured out yet. Here is an example token:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjBiZjM5OWIwYjRkNzY3OTE5MDFlYzlmNTUxNTZkMzdlIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1Mjc4OTA2MDAsImV4cCI6MTUyNzg5NDIwMCwiaXNzIjoiaHR0cDovL2NvbW1hbmRjZW50ZXI6NTAwMCIsImF1ZCI6Imh0dHA6Ly9jb21tYW5kY2VudGVyOjUwMDAvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoiY29tbWFuZGNlbnRlciIsInN1YiI6ImU3NzY4MjEyLTdhMGYtNDA4YS04YzlmLWZmNDRmOTVlNzc2ZSIsImF1dGhfdGltZSI6MTUyNzg5MDYwMCwiaWRwIjoibG9jYWwiLCJzY29wZSI6WyJvcGVuaWQiLCJwcm9maWxlIl0sImFtciI6WyJwd2QiXX0.N6j_8Mbc-WAZInLnhauBS_dC6my3ggJEFGNBuuanJXGG5Q1mL7K9KZ11cgxrWQEp6NRS07bZrh02qA0Hcd0pLJzg0xB49CR-kFlA8PWdZ-wnf1U4VGq7ZZY2PaxccqextF-s3V-ObEsnPq7k1csTBM_lBW5u4m907q3zcHfPtW9RNz3kELKSuW1vMOWksrOOaJO3UGAPJgRcFjlc5imPykKQVCiAO9UV1nuNUHOLOu4XDim-f3YHLU24J-vbgxkqdUtgs4Xm43b3AplYrkN_TJ0ba5demtnQNd5No0s6bkqkuPvVGz2G3MD5tWkQVnu7kV7GNfTOvJlInCkDOE6few
Taking this over to https://jwt.io/ you can see that the token parses out to be
{
"alg": "RS256",
"kid": "0bf399b0b4d76791901ec9f55156d37e",
"typ": "JWT"
}.
{
"nbf": 1527890600,
"exp": 1527894200,
"iss": "http://commandcenter:5000",
"aud": "http://commandcenter:5000/resources",
"client_id": "commandcenter",
"sub": "e7768212-7a0f-408a-8c9f-ff44f95e776e",
"auth_time": 1527890600,
"idp": "local",
"scope": [
"openid",
"profile"
],
"amr": [
"pwd"
]
}.
{SIGNATURE}
I've seen 2 main ways in which tokens are signed. One is just using some passpharase that everyone magically has to know in order to verify the token. We don't want to have to manage updating multiple clients when a passphrase changes, so this is not an option for us. The other way is to use essentially a PKI method much like how HTTPS keys are exchanged and authenticated using X509 certificates. I have a few questions about this and this particular example:
1. Why isn't the 3rd and final section of the token Base64 encoded? I'm guessing its Base64Url, is that right? The rest of the segments use Base64, why not this one as well?
2. We would like to use essentially SSL certificates for signing and I presume this example token has done the same, but how does the receiver know which public certificate it should fetch in order to validate this token? Shouldn't the certificate serial number be provided?
3. Why is this signature so big? This seems like way to many bytes for just a signature, is there other data packed inside?