I implemented a rest authorization server that returns the public-key for a given keyId in the JWK format using the com.nimbusds:nimbus-jose-jwt:9.13
package. The code looks something like this:
@RequestMapping(value = "/oauth2", produces = APPLICATION_JSON_VALUE) public interface Rest { ... @GetMapping("/public-key/{keyId}") @Operation(summary = "Return the public key corresponding to the key id") JWK getPublicKey(@PathVariable String keyId); } public class RestController implements Rest { ..... public JWK getPublicKey(String keyId) { byte[] publicKeyBytes = .... RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes)); JWK jwk = new RSAKey.Builder(publicKey) .keyID(keyId) .algorithm(new Algorithm(publicKey.getAlgorithm())) .keyUse(KeyUse.SIGNATURE) .build(); return jwk; } }
This code returns a JWK key in the following format:
{ "keyStore": null, "private": false, "publicExponent": {}, "modulus": {}, "firstPrimeFactor": null, "secondPrimeFactor": null, "firstFactorCRTExponent": null, "secondFactorCRTExponent": null, "firstCRTCoefficient": null, "otherPrimes": [], "requiredParams": { "e": "some-valid-exponent", "kty": "RSA", "n": "some-valid-modulus" }, "privateExponent": null, "x509CertChain": null, "algorithm": { "name": "RSA", "requirement": null }, "keyOperations": null, "keyID": "some-valid-key-id", "x509CertURL": null, "x509CertThumbprint": null, "x509CertSHA256Thumbprint": null, "parsedX509CertChain": null, "keyUse": { "value": "sig" }, "keyType": { "value": "RSA", "requirement": "REQUIRED" } }
On the client side (java), I try to parse the jwk with the following code:
public JWK getPublicKey(String keyId) { String json = restTemplate.getForObject(publicUrl + "/oauth2/public-key/" + keyId, String.class); try { return JWK.parse(json); } catch (ParseException e) { log.error("Unable to parse JWK", e); return null; } }
However, the client is unable to parse the key since parse
throws an exception (Missing parameter "kty"
). I see that JWK.parse
requires a kty
key in main JWT josn body, while the default serialization of JWK
embeds the kty
key within requiredParams
key. When I try jwk.toString()
, I do see the kty
key in the main json body.
Why doesn’t serialization/deserialization of the native JWK object work in a straight-forward manner? What would be the best way to fix this without implementing a custom jwt structure or a serializer/deserializer?
Update 1: This code will work if we change the return type from JWK
to Map<String, Object>
or String
and handle deserialization on the client-side. However, it would be better if the package natively does the (de)serialization for us.
Advertisement
Answer
The answer is to use String
for (de)serialization for those facing this problem. Why, you ask? According to the RFC, JWK is a string in the JSON format. While nimbusds:nimbus-jose-jwt
defines a JWK object, any APIs that return valid JWK (or JWKSet
) can assume that it’s a string.
I also raised this issue with the developers of this package, and they recommended using String
or Map<String, Object>
for (de)serialization.