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==
Related
I'm configuring an external identity provider in my Keycloak instance and trying to get it to validate the tokens using a external JWKS URL. Using the converted PEM from JWKS works fine, the using the URL is not working.
The token validation fails upon login with the following message:
[org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (default task-4) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
I debugged the Keycloak server get more on the problem and found a "problem" in class JWKSUtils:
/**
* #author Marek Posolda
*/
public class JWKSUtils {
//...
public static Map<String, KeyWrapper> getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
Map<String, KeyWrapper> result = new HashMap<>();
for (JWK jwk : keySet.getKeys()) {
JWKParser parser = JWKParser.create(jwk);
if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
KeyWrapper keyWrapper = new KeyWrapper();
keyWrapper.setKid(jwk.getKeyId());
keyWrapper.setAlgorithm(jwk.getAlgorithm());
keyWrapper.setType(jwk.getKeyType());
keyWrapper.setUse(getKeyUse(jwk.getPublicKeyUse()));
keyWrapper.setVerifyKey(parser.toPublicKey());
result.put(keyWrapper.getKid(), keyWrapper);
}
}
return result;
}
//...
}
The if fails with a NullPointerException because the call jwk.getPublicKeyUse() returns null.
But I found out that it's null because the JWKS URL returns a single key without the attribute use, which is optional according to the specification. [https://www.rfc-editor.org/rfc/rfc7517#section-4.2]
Keycloak only accepts JWKS URLs that return all keys with the attribute use defined. But the IdP I'm trying to connect does not return that attribute in the key.
Given that situation, to who should I file an issue, the IdP or to Keycloak? Or is there something I'm doing wrong in the configuration?
I filed an issue with Keycloak about this exact problem in August 2019.
Their answer:
Consuming keys without validating alg and use is dangerous as such
Keycloak requires these to be present.
In my case, I contacted the IdP and they were able to populate the "use" parameter. If that is not an option, then you're pretty much stuck with your workaround.
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.");
}
New to JWT
I want to verify my string token which is generated as below
String productkey:which is signed and encoded format.
String publickey:Generated key from simmulator and store as string
JWSVerifier verifier= new ECSDVerifier(ECKey.parse(publickey))
Boolean test=verifier.verify(productkey);
Please suggest which appropriate method I have to used.
If you are new to JWT, I would suggest you to use JJwt API. You can easily sign your tokens and verify them.
Snippet to generate JWT token:
Jwts.builder()
.setClaims(payload)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.RS256, privateKey )
.compact();
Snippet to verify the token with public key:
Jwts.parser()
.setSigningKey(publicKey )
.parseClaimsJws(jwtToken)
JJwt Maven/Gradle link
Hope this helps.
Is there a Scala implementation of JWT or at least an example with Play? After having posted this question I searched further on the Internet and found some intros to JWT. Any suggestion would be really appreciated.
I am using Nimbus-JOSE-JWT in a spray.io app and am quite happy with it. The object performing authentication extends HttpAuthenticator and if it finds a correct JWT it returns the token subject and related info, otherwise None (authentication fails). With Play2 you can implement HTTP Basic Auth with something like this. Regarding token setting/getting which I assume is of more interest to you:
First, create a private/public key pair (I used parts of this code). Create the authentication object that loads the keys on initialization from the filesystem.
Create a a com.nimbusds.jose.crypto.MACSigner and a com.nimbusds.jose.crypto.MACVerifier using these keys.
Whenever you want to set a key, FIRST encrypt it, THEN sign it. Encryption:
private def encrypt(subject: String) = {
val header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A128GCM)
val jwt = new EncryptedJWT(header, claimSet(subject))
val encrypter = new RSAEncrypter(publicKey.asInstanceOf[java.security.interfaces.RSAPublicKey])
jwt.encrypt(encrypter)
jwt.serialize()
}
The claimSet method predictably returns a set of claims:
def claimSet(subject: String) = {
val jwtClaims = new JWTClaimsSet()
jwtClaims.setIssuer(Config.JWT.issuer)
jwtClaims.setSubject(subject)
jwtClaims.setJWTID(java.util.UUID.randomUUID().toString())
jwtClaims
}
The publicKey property is the value returned from KeyFactory.getInstance("RSA").generatePublic.
Signing:
private def sign(jwt: String) = {
val jwsObject = new JWSObject(new JWSHeader(JWSAlgorithm.HS256), new Payload(jwt))
jwsObject.sign(Tokens.signer)
jwsObject.serialize
}
Given the above, when you receive a key you need to verify the signature first, then decrypt it. For verification, first you try to parse it with com.nimbusds.jose.JWSObject.parse(my_token) and as long as it doesn't throw a ParseException you call verify on the JWSObject that parse returns, using as an argument the MACVerifier that you created earlier. If verify returns true, you only need to call getPayload.toString on the same JWSObject to get the verified payload.
To decrypt the verified payload you call com.nimbusds.jwt.EncryptedJWT.parse on it, then something like:
val decrypter = new RSADecrypter(privateKey.asInstanceOf[java.security.interfaces.RSAPrivateKey])
jwt.decrypt(decrypter)
privateKey is the value returned from KeyFactory.getInstance("RSA").generatePrivate.
You can then get the claim set with jwt.getJWTClaimsSet.
Finally, with regard to setting the Authorization header, I'm doing this on my AngularJS client using principles also mentioned in this post.