As we discussed in our previous article, security is a substantial part of the development of web applications. We do not want sensitive information to be disclosed to unauthorized parties. Hence, we devote our efforts to securing our APIs and ensuring that only authorized users have access to the information.
In the first part of this tutorial, we learned how to enable Vertx Web as a resource server. We identified the need to protect our resources and created a mechanism to enforce every client to send a JWT along with every request to our server. This mechanism works like a charm, but there are still parts of our code that could be better, namely: it is not a good practice to have the public key hardcoded in our app.
A more suitable alternative would be to have the public key in a separate configuration file and read it from there. This file can be encrypted to make our app safer. There's also a third more-elegant alternative: to consume the public key dynamically from the authorization server.
In this article, we're gonna learn how to get the public key from our authorization server dynamically. Let's start.
Pre-requirements
Introducing the Authorization Server
Keycloak is an open source Identity and Access Management solution aimed at modern applications and services. It makes it easy to secure applications and services with little to no code. - Keycloak.org
- Realm URI:
- Content: It's the base URL of the realm. We could actually get the public key from this URI, but - in my experience- it is better to get it from the dedicated JWKS URI.
- Example: http://localhost:8083/auth/realms/baeldung/
- Well-known URI:
- Content: As part of the OIDC Discovery specification, the well-known URI offers information about the configuration of the OIDC provider. This includes information about other endpoints, such as: authorization, token generation, token introspection, jwks, among others.
- Example: http://localhost:8083/auth/realms/baeldung/.well-known/openid-configuration
- JWKS URI:
- Content: The JSON Web Key Set (JWKS) is a set of keys containing the public keys used to verify any JSON Web Token (JWT) issued by the authorization server and signed using the RS256 signing algorithm.
- Example: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs
Developing the App
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.14.0</version>
</dependency>
private String getKey() {
try {
JwkProvider provider = new UrlJwkProvider(new URL(JWKS_URL));
Jwk jwk = provider.get(JWKS_KID);
return Base64.getEncoder().encodeToString(jwk.getPublicKey().getEncoded());
} catch (Exception e) {
return "";
}
}
private final String JWKS_URL = "http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs";
private final String JWKS_KID = "_b78X30O343js3QZcvCJSSHa4zUKPmIBchQmHcNpBUM";
public class VertxHttpVerticle extends AbstractVerticle {
private final String JWKS_URL = "http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs";
private final String JWKS_KID = "_b78X30O343js3QZcvCJSSHa4zUKPmIBchQmHcNpBUM";
@Override
public void start() {
Router router = configureRouter();
vertx.createHttpServer().requestHandler(router).listen(8080);
}
private Router configureRouter() {
Router router = Router.router(vertx);
router.route("/").handler(routingContext -> {
routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "text/html")
.end("<h1>The Vertx server is running!</h1>");
});
router.route("/api/*").handler(this::validateTokens);
router.route("/api/users").handler(routingContext -> {
routingContext.response().setStatusCode(200);
routingContext.response().end("Here goes a secret list of users");
});
router.route("/api/secret").handler(routingContext -> {
routingContext.response().setStatusCode(200);
routingContext.response().end("This is a super SECRET endpoint");
});
router.route("/api/admin").handler(routingContext -> {
routingContext.response().setStatusCode(200);
routingContext.response().end("Only users with a valid token have access to this endpoint");
});
return router;
}
private void validateTokens(RoutingContext routingContext) {
JWTAuth provider = JWTAuth.create(vertx, new JWTAuthOptions()
.addPubSecKey(new PubSecKeyOptions(new JsonObject().put("algorithm", "RS256").put("publicKey", getKey()))));
String jwt = routingContext.request().getHeader("Authorization");
if (jwt != null && !jwt.equals("")) {
jwt = jwt.substring(7);
provider.authenticate(new JsonObject().put("jwt", jwt),
result -> {
if (result.succeeded()) {
routingContext.next();
} else {
setUnauthorized(routingContext);
}
});
} else {
setUnauthorized(routingContext);
}
}
private String getKey() {
try {
JwkProvider provider = new UrlJwkProvider(new URL(JWKS_URL));
Jwk jwk = provider.get(JWKS_KID);
return Base64.getEncoder().encodeToString(jwk.getPublicKey().getEncoded());
} catch (Exception e) {
return "";
}
}
private void setUnauthorized(RoutingContext routingContext) {
routingContext.response().setStatusCode(401);
routingContext.response().end();
}
}
Comments
Post a Comment