springspring-bootnimbus-jose-jwteddsa

How to decode and verify EdDSA JWT in Spring Security


I'm trying to decode and verify an EdDSA JWT using Spring Security in the role of a Resource Server. Spring doesn't seem to want to support EdDSA, so I started writing my own JwtDecoder that looks like this

class CustomJwtDecoder : JwtDecoder {

    override fun decode(token: String): Jwt {
        val signedJwt = SignedJWT.parse(token)

        try {
            val claimsSet = signedJwt.jwtClaimsSet
            val headers = signedJwt.header
            return Jwt(
                token,
                claimsSet.issueTime.toInstant(),
                claimsSet.expirationTime.toInstant(),
                headers.toJSONObject(),
                claimsSet.claims,
            )
        } catch (ex: JOSEException) {
            throw RuntimeException("Failed to decode JWT: ${ex.message}", ex)
        }
    }
}

For verifying these JWTs, I started looking at using tink via this dependency

<dependency>
  <groupId>com.google.crypto.tink</groupId>
  <artifactId>tink</artifactId>
  <version>1.6.1</version>
</dependency>

However, when trying to verify a token with this library, the advice is to do something like this

val verifier: JWSVerifier = Ed25519Verifier(publicJWK)

But in this case, the publicJwk here has to come from the jwks uri on the authentication server by mapping the kid claim of the signedJwt to the public key on the jwks uri. So the question is can I get the publicJwk from the jwks uri using standard spring mechanisms, or will I have to completely fork the nimbus-jose library to add support for EdDSA and then pull that into my project to override the Spring Security dependency?

Additionally, does the token verification have to go in the JwtDecoder, or does the verification come in in a different step of the Spring authN/authZ process? And how should that verification step look in this context?

If there's a better/simpler way to decode/verify the EdDSA JWT in the context of Spring Security, then I'd love to hear that as well.


Solution

  • To decode and verify EdDSA JWTs in Spring Security, you will need to integrate a library that supports EdDSA signatures, such as Nimbus JOSE+JWT or Google tink (soon to be in tink-crypto ), as you mentioned.

    You would need to:

    • fetch the public key corresponding to the private key that was used to sign the JWT (to check the signature). Typically, this is available through a JWKS (JSON Web Key Set) endpoint. You can use RestTemplate or any HTTP client to fetch the JWKS and parse it to get the public key.

    • verify the signature of the JWT (using the public key). Since you are using Tink, you can use Ed25519Verifier as you have mentioned. Alternatively, if you decide to use Nimbus JOSE+JWT, you can use Ed25519Verifier from that library.

    Your custom JwtDecoder should be responsible for both decoding the token and verifying its signature.
    Once your custom JwtDecoder is implemented, you will need to integrate it with Spring Security. You can do this by configuring it as a bean and using it as the JwtDecoder in your security configuration.

    For instance:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PublicKey publicKey; // Inject the public key
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.oauth2ResourceServer()
                .jwt()
                .decoder(customJwtDecoder(publicKey));
        }
    
        @Bean
        public JwtDecoder customJwtDecoder(PublicKey publicKey) {
            return token -> {
                try {
                    SignedJWT signedJwt = SignedJWT.parse(token);
                    JWSVerifier verifier = new Ed25519Verifier(publicKey);
                    if (!signedJwt.verify(verifier)) {
                        throw new JOSEException("Failed to verify JWT signature");
                    }
    
                    JWTClaimsSet claimsSet = signedJwt.getJWTClaimsSet();
                    return Jwt.withTokenValue(token)
                        .header(signedJwt.getHeader().toJSONObject())
                        .claims(claimsSet::getClaims)
                        .build();
                } catch (JOSEException | ParseException ex) {
                    throw new RuntimeException("Failed to decode JWT: " + ex.getMessage(), ex);
                }
            };
        }
    
        @Bean
        public PublicKey getPublicKey() {
            // Logic to fetch the public key from JWKS endpoint
            // ...
        }
    }
    

    This would show how you can create a custom JwtDecoder that verifies the signature of JWTs using the EdDSA algorithm and integrate it into Spring Security. The getPublicKey method should contain the logic for fetching the public key from the JWKS endpoint. You can use Spring's RestTemplate for this purpose.

    That should allow you to integrate EdDSA JWTs into Spring Security without having to fork the Nimbus JOSE+JWT library or replace Spring Security's dependencies.