JSON Web Key Set (JWKS) is a set of keys containing public keys that can be used to verify given JSON Web Token (JWT) by the authorization server. In this article I will show you how to add JWKS authentication with Micronaut.
JWKS Use Case
You have third party authorization server like Auth0 or FusionAuth. This authorization server can generate a JWT token when users login or authenticate. That generated JWT token must be signed by asymmetric key, for example RSA. JWKS collects the public keys and available for anyone to download it.
Your API server handle authenticated request. Users send a request and include Bearer token authorization. API server should validate if the token is valid. If token is not valid then return 401 immediately, on the other hand if token is valid then proceed to handle the request.
Benefit Of JWKS
With JWKS, your API server just need to download the JWKS from authorization server and use the public keys to validate the JWT signature.
Using JWKS makes key management and distribution easier because JWKS requires asymmetric keys that makes keys distribution easy and secure. If those keys are revoked, your API server can re-download new keys.
JKWS requires asymmetric keys like RSA or Elliptic Curve. JWKS doesn’t support symmetric keys.
JWKS is usually exposed as endpoint
https://{oauth-provider.com}/.well-known/jwks.json
{
"keys": [
{
"alg": "RS256",
"e": "AQAB",
"kid": "GTYXzCE-reQAK75lK2OVy3-7Sl8",
"kty": "RSA",
"n": "hnC0RAJF95Eccx-tGE0ARKkVMt8gc-Qsgc-XZg-sPzj2kiJ1cbpMtKr2NXzo2WFGFeo_6ynMFOKYjjCsTn9pW1Luee4FgFQsruY4020HEq0pX1SeEpj3JKVbyPeb2y-1zXeunudztSwrTZIY6d9cmDDFZF2A1_gYHEFsZrO-CSkZvPLqfceaChRksDFTAM66A3UUs3VIuRL2JpCVfIDSToAQCnoLwCWBT3m35CUQWk0BRjeMYDA_ugoEjwTzcx1UIX7QP5xRbCPlDSefaKXSurU9pVU6a_vOf1Y-LqKRBq9cNDXZ2Cx0IFvp-FLXJB-ztMzmmjVlBzl3GZxFlwWdvw",
"use": "sig",
"x5c": [
"MIICrzCCAZegAwIBAQIRAMoIhhMZl0W5gU5AbsvRNxUwDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAxMIYWNtZS5jb20wHhcNMjEwNTA1MjIyMTM2WhcNMzEwNTA1MjIyMTM2WjATMREwDwYDVQQDEwhhY21lLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIZwtEQCRfeRHHMfrRhNAESpFTLfIHPkLIHPl2YPrD849pIidXG6TLSq9jV86NlhRhXqP+spzBTimI4wrE5/aVtS7nnuBYBULK7mONNtBxKtKV9UnhKY9ySlW8j3m9svtc13rp7nc7UsK02SGOnfXJgwxWRdgNf4GBxBbGazvgkpGbzy6n3HmgoUZLAxUwDOugN1FLN1SLkS9iaQlXyA0k6AEAp6C8AlgU95t+QlEFpNAUY3jGAwP7oKBI8E83MdVCF+0D+cUWwj5Q0nn2il0rq1PaVVOmv7zn9WPi6ikQavXDQ12dgsdCBb6fhS1yQfs7TM5po1ZQc5dxmcRZcFnb8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEARfb3g1dJB4jVzTookwg5W/qRgSktAYj6LrT39VOjo7pIUQJ9Z/bDXy61XSQdgP/NM0vgMfdolCAJTYDaLBZ9oea61BhpGPZb6LQQV1MIwGJaZg6Ti4SNUIZ2Y7cwtcIY8Vxw4bhQeXNZ89RFmtByGLKfwgdraK8erXVSiBLzo86qCrYmc82LoFi3FOuiT2GkM1p9SZLIkaIWJZ/Lio4oBXuoy2rIeSnt37mJJhs5bXkWSu3sSWLe6e87sccWeNhaQ3hXhcQi5nNggtp6DuR1WNQQQ9V8HCQFELVWAHX1N3H/YlbRKXmSHABBJzM3sJBvZKgiv5drnqg2sS8zSKQu+w=="
],
"x5t": "GTYXzCE-reQAK75lK2OVy3-7Sl8",
"x5t#S256": "TtXr4q4Um_JT64_-sr_mifjsaGCrfPb7j94Nj2s0gAY"
}
}
Micronaut
Micronaut is java framework to build backend applications in a very fast and lightweight fashion. Micronaut makes us integration JWKS authentication with Micronaut very easy. You could just add this configuration in application.yml:
micronaut:
application:
name: api-server
server:
port: 8000
security:
enabled: true
token:
enabled: true
jwt:
enabled: true
signatures:
jwks:
fusionauth:
url: ${AUTH_JKWS:`http://fusionauth:9011/.well-known/jwks.json`}
At the time I’m writing this article, Micronaut only support RSA keys and doesn’t support Eliptic Curve keys.
Micronaut Dependencies
Add this Micronaut security dependencies in your pom.xml files (I’m using Maven)
<dependency>
<groupId>io.micronaut.security</groupId>
<artifactId>micronaut-security</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.security</groupId>
<artifactId>micronaut-security-annotations</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.security</groupId>
<artifactId>micronaut-security-jwt</artifactId>
</dependency>
Read also: Golang web template
Micronaut Secured Controller
Next, we will add secured controller code to project. Micronaut uses @secured annotation to make the controller secured.
@Controller("/user")
@Secured(IS_AUTHENTICATED)
@ExecuteOn(TaskExecutors.IO)
public class UserController {
@Get
public User getUser(Principal principal) {
UUID userUuid = UUID.fromString(principal.getName());
UserResponse response = fusionAuthClientDelegate.execute(client -> client.retrieveUser(userUuid));
return response.user;
}
}
Build your project and run it, then send request using curl on new terminal.
curl -X GET http://localhost:8000/user \
-H "Authorization: Bearer <JWT_TOKEN>"
Final Words
Micronaut makes JWKS integration easy, but it is also hard to figure out when things doesn’t works. On my experience here some steps that I have done when I keep getting 401 Unauthorized.
- Use Wireshark to capture the http request packet to make sure Micronaut send request to JWKS endpoint.
- Make sure your JWT is valid by validating it using your authorization server endpoint. For example by sending request GET http://fusionauth:9011/oauth2/userinfo
- Increase log level to DEBUG.