Hi there! In my last blog post Easy SSO for Vert.x with Keycloak, we learned how to configure single sign-on for a Vert.x web application with Keycloak and OpenID connect. This time, we’ll see how we can protect an application with Vert.x’s JWT Authorization support and Keycloak.
Keycloak Setup
To secure our Vert.x app, we need to use a Keycloak server for obtaining JWT tokens. Although Keycloak has a great getting started guide I wanted to make it a bit easier to put everything together, therefore I prepared a local Keycloak docker container as described here, which comes with all the required configuration in place, that you can start easily.
The preconfigured Keycloak realm vertx contains a vertx-service OpenID connect client for our Vert.x app and a set
of users for testing. To ease testing, the vertx-service is configured with Direct Access Grant enabled in Keycloak, which
enables support for the OAuth2 resource owner password credentials grant (ROPC) flow.
To start Keycloak with the preconfigured realm, just start the docker container with the following command:
Vert.x App
The example app consists of a single Verticle, that runs on http://localhost:3000 and provides a few routes with protected resources. You can find the complete example here.
Our web app contains the following protected routes with handlers:
/api/greet - The greeting resource, which returns a greeting message, only authenticated users can access this resource.
/api/user - The user resource, which returns some information about the user, only users with role user can access this resource.
/api/admin - The user resource, which returns some information about the admin, only users with role admin can access this resource.
This example is built with Vert.x version 3.9.3.
Running the app in the console
To run the app, we need to build it first:
This creates a jar, which we can run:
Note, that we need to start Keycloak first, since our app fetches the configuration from Keycloak on startup.
Running the app in the IDE
We can also run the app directly from your favourite IDE like IntelliJ Idea or Eclipse.
To run the app from an IDE, we need to create a launch configuration and use the main class io.vertx.core.Launcher. Then set the the program arguments to
run demo.MainVerticle and use the classpath of the jwt-service-vertx module.
With that in place we should be able to run the app.
JWT Authorization
JWT Foundations
JSON Web Token (JWT) is an open standard to securely exchange information between two parties in the form
of Base64URL encoded JSON objects.
A standard JWT is just a string which comprises three base64url encoded parts header, payload and a signature, which are separated by a “.” character.
There are other variants of JWT that can have more parts.
An example JWT can look like this:
The header and payload sections contain information as a JSON object, whereas the signature is just a plain string. JSON objects contain key value pairs which are called claims.
The claims information can be verified and trusted because it is digitally signed with the private key from a public/private key-pair.
The signature can later be verified with a corresponding public key. The identifier of the public/private key-pair used to sign a JWT can be
contained in a special claim called kid (key identifier) in the header section of the JWT.
An example for a JWT header that references a public/private key-pair looks like this:
It is quite common to use JWTs to convey information about authentication (user identity) and authorization (scopes, user roles, permissions and other claims).
OpenID providers such as Keycloak support issuing OAuth2 access tokens after authentication for users to clients in the form of JWTs.
An access token can then be used to access other services or APIs on behalf of the user. The server providing those services or APIs is often called resource server.
An example JWT payload generated by Keycloak looks like this:
If a resource server receives a request with such an access token, it needs to verify and inspect the token before it can trust its content.
To verify the token, the resource server needs to obtain the public key to check the token signature.
This public key can either be configured statically or fetched dynamically from the OpenID Provider by leveraging the kid information from the JWT header section.
Note that most OpenID providers, such as Keycloak, provide a dedicated endpoint for dynamic public key lookups, e.g. http://localhost:8080/auth/realms/vertx/protocol/openid-connect/certs.
A standard for providing public key information is JSON Web Key Set (JWKS).
The JWKS information is usually cached by the resource server to avoid the overhead of fetching JWKS for every request.
An example response for Keycloak’s JWKS endpoint looks like this:
The keys array contains the JWKS structure with the public key information that belongs to the public/private key-pair which was used
to sign the JWT access token from above. Note the matching kid claim from our earlier JWT header example.
Now that we have the appropriate public key, we can use the information from the JWT header to validate the signature of the JWT access token.
If the signature is valid, we can go on and check additional claims from the payload section of the JWT, such as expiration, allowed issuer and audience etc.
Now that we have the necessary building blocks in place, we can finally look at how to configure JWT authorization in Vert.x.
JWT Authorization in Vert.x
Setting up JWT authorization in Vert.x is quite easy. First we need to add the vertx-auth-jwt module as a dependency to our project.
In our example, the whole JWT authorization setup happens in the method setupJwtAuth.
We use a WebClient to dynamically fetch the public key information from the /protocol/openid-connect/certs JWKS endpoint relative to our Keycloak issuer URL.
After that, we configure a JWTAuth instance and customize the JWT validation via JWTOptions and JWTAuthOptions.
Note that we use Keycloak’s realm roles for role based authorization via the JWTAuthOptions#setPermissionsClaimKey(..) method.
Protecting routes with JWTAuthHandler
Now that our JWTAuth is configured, we can use the JWTAuthHandler in the setupRouter method to apply
JWT authorization to all routes matching the path pattern /api/*. The JWTAuthHandler validates received JWTs and performs
additional checks like expiration and allowed issuers. With that in place, we configure our actual routes in setupRoutes.
Extracting user information from JWTUser
To access user information in our handleGreet method, we cast the result of the io.vertx.ext.web.RoutingContext#user method to JWTUser
which allows us to access token claim information via the io.vertx.ext.auth.jwt.impl.JWTUser#principal JSON object.
If we’d like to use the JWT access token for other service calls, we could extract the token from the Authorization header.
Obtaining an Access Token from Keycloak for user tester
To test our application we can use the following curl commands in a bash like shell to obtain an JWT access token to call one
of our endpoints as the user tester with the role user.
Note that this example uses the cli tool jq for JSON processing.
Here we use the JWT access token in the Authorization header with the Bearer prefix to call our greet route:
Example output:
Applying Role-based Access-Control with JWTUser
To leverage support for role based access control (RBAC) we can use the io.vertx.ext.auth.User#isAuthorised method
to check whether the current user has the required role. If the role is present we return some data about the user, otherwise
we send a response with status code 403 and a forbidden error message.
Output:
Output:
Obtaining an Access Token from Keycloak for user vadmin
To check access with an admin role, we obtain a new token for the user vadmin which has the roles admin and user.
Output:
Output:
Conclusion
We learned how to configure a Vert.x application with JWT authorization powered by Keycloak. Although the configuration is quite complete
already, there are still some parts that can be improved, like the dynamic JWKS fetching on public-key pair rotation as well as extraction of nested roles.
Nevertheless this is a good starting point for securing your own Vert.x services with JWT and Keycloak.
Thank you for your time, stay tuned for more updates! If you want to learn more about Keycloak, feel free to reach out to me. You can find me via thomasdarimont on twitter.