jjwt icon indicating copy to clipboard operation
jjwt copied to clipboard

Support a way to use JWK keys

Open leoconco opened this issue 7 years ago • 13 comments

Would be really useful to support Key suppliers based on a JWS endpoint for the validation of a JWS, or any way to use JWK to verify a JWS (JWKParser?).

leoconco avatar Jul 12 '17 21:07 leoconco

+1 on this. I recently created a solution that I'd like to integrate into JJWT. I think where we should draw the line is to handle retrieval of jwk given an endpoint. Since it is guaranteed by the spec to be open and will always be a simple GET, I also think we can do this without the need to involve another external library. Here's the solution I created for a "quick and dirty" example. I will formalize it with tests to be incorporated into the jjwt.

    @SuppressWarnings("unchecked")
    private Map<String, String> getKeyById(Map<String, Object> jwks, String kid) {
        List<Map<String, String>> keys = (List<Map<String, String>>)jwks.get("keys");
        Map<String, String> ret = null;
        for (int i = 0; i < keys.size(); i++) {
            if (keys.get(i).get("kid").equals(kid)) {
                return keys.get(i);
            }
        }
        return ret;
    }

    private String simpleGet(String urlStr) {
        HttpURLConnection connection = null;

        try {
            URL url = new URL(urlStr);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");

            BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = rd.readLine()) != null) {
                response.append(line);
                response.append('\r');

            }
            rd.close();
            return response.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

With these two methods, it's easy to parse a JWT like so (NOTE: This example use jackson mapper for json, which is already incorporated into JJWT):

...
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> jwks = mapper.readValue(simpleGet(jwksUri), typeRef);

SigningKeyResolver resolver = new SigningKeyResolverAdapter() {
    public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
        try {
            Map<String, String> jwk = getKeyById(jwks, jwsHeader.getKeyId());
            BigInteger modulus = new BigInteger(1, Base64.getUrlDecoder().decode(jwk.get("n")));
            BigInteger exponent = new BigInteger(1, Base64.getUrlDecoder().decode(jwk.get("e")));

            return KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            log.error("Failed to resolve key: {}", e.getMessage(), e);
            return null;
        }
    }
};

try {
    Jws<Claims> jwsClaims = Jwts.parser()
        .setSigningKeyResolver(resolver)
        .parseClaimsJws(jwt);

    return jwsClaims.getBody();
} catch (Exception e) {
    log.error("Couldn't parse jwt: {}", e.getMessage(), e);
    return null;
}
...

dogeared avatar Jul 25 '17 14:07 dogeared

TIL that JWK support is already in progress as it's a requirement for JWE support. It's not ready yet, but you can see progress here: https://github.com/jwtk/jjwt/compare/jwe#diff-9bcd9bdeb445b83fc3620c7f1ca1e87fR1

dogeared avatar Jul 25 '17 15:07 dogeared

+1 for this being part of this library. In the mean time while we wait. the auth0/jwks-rsa-java project plays well with a SigningKeyResolver

https://github.com/auth0/jwks-rsa-java

Something like

import com.auth0.jwk.JwkProvider;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;

import java.security.Key;

public class JwkKeyResolver implements SigningKeyResolver {
    private JwkProvider keyStore;

    public JwkKeyResolver(JwkProvider keyStore) {
        this.keyStore = keyStore;
    }

    @Override
    public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
        String keyId = jwsHeader.getKeyId();
        Key pub = keyStore.get(keyId);
        return pub;
    }

}

ryber avatar Nov 02 '17 18:11 ryber

I think it would be nice to have this in Version 1.0

chrisaige avatar May 29 '19 15:05 chrisaige

If I can contribute in any way. Hit me up currently I have to use 2 Libaries and I want to only use this one

chrisaige avatar Jun 14 '19 13:06 chrisaige

Any progress on this feature ?

mancave avatar Nov 26 '19 10:11 mancave

Any updates?

hyperxpro avatar Jun 02 '20 10:06 hyperxpro

As well as parsing externally generated JWKs it would also be useful to directly support formatting keys generated by the library as JWKs. The API provides essentially a black box for generating keys from a SignatureAlgorithm and it would be nice to be able to generate the corresponding JWK needed to verify a JWT equally easily. Itsnot hard to do by hand but not without engaging with details the library normally hides from you.

spbooth avatar Jun 08 '20 15:06 spbooth

Found this library for dealing with JWKs. Hope it helps someone else in the future:

<!-- https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt -->
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>8.19</version>
</dependency>

https://connect2id.com/products/nimbus-jose-jwt

hyperxpro avatar Jun 08 '20 15:06 hyperxpro

@hyperxpro this will be in the JJWT 1.0 release.

lhazlewood avatar Jun 08 '20 16:06 lhazlewood

any update on 1.0 release date with JWK support?

birender-s avatar Jun 30 '20 08:06 birender-s

Any updates?

ReDestroyDeR avatar Feb 28 '22 19:02 ReDestroyDeR

@ReDestroyDeR in the short term you can create a SigningKeyResolver that looks something like:

https://github.com/okta/okta-jwt-verifier-java/blob/master/impl/src/main/java/com/okta/jwt/impl/jjwt/RemoteJwkSigningKeyResolver.java

bdemers avatar Feb 28 '22 22:02 bdemers

It's unfortunate that the Okta implementation caches the keys indefinitely though. Ideally it should be cached for a certain amount of time, configurable or based on the HTTP Cache-Control response headers.

ddevrien avatar Feb 03 '23 12:02 ddevrien

@ddevrien that impl could absolutely be improved, by the keys are not cached indefinitely (under normal usage), they will get updated when a new key-id is used, that is not a perfect solution.

Obligatory cache quote:

There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton

A generic full-featured implementation should probably:

  • Cache keys for length based on Cache-Control headers
  • Have a configurable fallback cache length for servers without Cache-Control headers
  • Replace cached keys when new key-ids are found (remote keys could be manually rotated, which would invalidate the existing local cache)
  • Have a minimum (configurable?) duration between HTTP requests (to prevent potential thread starvation)
  • While new keys are being requested, old keys should continue to validate tokens (remote key rotation where old and new keys overlap)
  • potentially support functionality to programmatically clear cache (if a key leaks, you may want to force cache invalidation).

bdemers avatar Feb 03 '23 16:02 bdemers

Full JWK support has been merged with #279 and will be released in the upcoming 0.12.0 release!

lhazlewood avatar Aug 11 '23 20:08 lhazlewood

#178 was closed (not merged) in 2016; maybe you meant #279?

jglick avatar Aug 11 '23 21:08 jglick

Oh dang, you're right, thank you!

lhazlewood avatar Aug 11 '23 22:08 lhazlewood