JWT Authorization for Vert.x with Keycloak

TL;DR:

In this blog post you’ll learn:

  • JWT foun­da­tions
  • How to pro­tect routes with a JWT Au­tho­riza­tion
  • How to ex­tract claims from a JWT en­coded token
  • How to apply RBAC with Key­cloak Realm roles

Hello again

Hi there! In my last blog post Easy SSO for Vert.x with Key­cloak, we learned how to con­fig­ure sin­gle sign-​on for a Vert.x web ap­pli­ca­tion with Key­cloak and OpenID con­nect. This time, we’ll see how we can pro­tect an ap­pli­ca­tion with Vert.x’s JWT Au­tho­riza­tion sup­port and Key­cloak.

Keycloak Setup

To se­cure our Vert.x app, we need to use a Key­cloak server for ob­tain­ing JWT to­kens. Al­though Key­cloak has a great get­ting started guide I wanted to make it a bit eas­ier to put every­thing to­gether, there­fore I pre­pared a local Key­cloak docker con­tainer as de­scribed here, which comes with all the re­quired con­fig­u­ra­tion in place, that you can start eas­ily.

The pre­con­fig­ured Key­cloak realm vertx con­tains a vertx-service OpenID con­nect client for our Vert.x app and a set of users for test­ing. To ease test­ing, the vertx-service is con­fig­ured with Direct Access Grant en­abled in Key­cloak, which en­ables sup­port for the OAuth2 re­source owner pass­word cre­den­tials grant (ROPC) flow.

To start Key­cloak with the pre­con­fig­ured realm, just start the docker con­tainer with the fol­low­ing com­mand:

docker run \
  -it \
  --name vertx-keycloak \
  --rm \
  -e KEYCLOAK_USER=admin \
  -e KEYCLOAK_PASSWORD=admin \
  -e KEYCLOAK_IMPORT=/tmp/vertx-realm.json \
  -v $PWD/vertx-realm.json:/tmp/vertx-realm.json \
  -v $PWD/data:/opt/jboss/keycloak/standalone/data \
  -p 8080:8080 \
  quay.io/keycloak/keycloak:11.0.2

Vert.x App

The ex­am­ple app con­sists of a sin­gle Verticle, that runs on http://localhost:3000 and pro­vides a few routes with pro­tected re­sources. You can find the com­plete ex­am­ple here.

Our web app con­tains the fol­low­ing pro­tected routes with han­dlers:

  • /api/greet - The greet­ing re­source, which re­turns a greet­ing mes­sage, only au­then­ti­cated users can ac­cess this re­source.
  • /api/user - The user re­source, which re­turns some in­for­ma­tion about the user, only users with role user can ac­cess this re­source.
  • /api/admin - The user re­source, which re­turns some in­for­ma­tion about the admin, only users with role admin can ac­cess this re­source.

This ex­am­ple is built with Vert.x ver­sion 3.9.3.

Running the app in the console

To run the app, we need to build it first:

cd jwt-service-vertx
mvn clean package

This cre­ates a jar, which we can run:

java -jar target/*.jar

Note, that we need to start Key­cloak first, since our app fetches the con­fig­u­ra­tion from Key­cloak on startup.

Running the app in the IDE

We can also run the app di­rectly from your favourite IDE like In­tel­liJ Idea or Eclipse. To run the app from an IDE, we need to cre­ate a launch con­fig­u­ra­tion and use the main class io.vertx.core.Launcher. Then set the the pro­gram ar­gu­ments to run demo.MainVerticle and use the class­path of the jwt-service-vertx mod­ule. With that in place we should be able to run the app.

JWT Authorization

JWT Foundations

JSON Web Token (JWT) is an open stan­dard to se­curely ex­change in­for­ma­tion be­tween two par­ties in the form of Base64URL en­coded JSON ob­jects. A stan­dard JWT is just a string which com­prises three base64url en­coded parts header, pay­load and a sig­na­ture, which are sep­a­rated by a ”.” char­ac­ter. There are other vari­ants of JWT that can have more parts.

An ex­am­ple JWT can look like this:

eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjN00xX2hkWjAtWDNyZTl1dmZLSFRDUWRxYXJQYnBMblVJMHltdkF0U1RzIn0.eyJleHAiOjE2MDEzMTg0MjIsImlhdCI6MTYwMTMxODEyMiwianRpIjoiNzYzNWY1YTEtZjFkNy00NTdkLWI4NjktYWQ0OTIzNTJmNGQyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL3ZlcnR4IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjI3YjNmYWMwLTlhZWMtNDQyMS04MWNmLWQ0YjAyNDI4ZjkwMSIsInR5cCI6IkJlYXJlciIsImF6cCI6InZlcnR4LXNlcnZpY2UiLCJzZXNzaW9uX3N0YXRlIjoiNjg3MDgyMTMtNDBiNy00NThhLWFlZTEtMzlkNmY5ZGEwN2FkIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwidXNlciJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYW1lIjoiVGhlbyBUZXN0ZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0ZXIiLCJnaXZlbl9uYW1lIjoiVGhlbyIsImZhbWlseV9uYW1lIjoiVGVzdGVyIiwiZW1haWwiOiJ0b20rdGVzdGVyQGxvY2FsaG9zdCJ9.NN1ZGE3f3LHE0u7T6Vfq5yPMKoZ6SmrUxoFopAXZm5wVgMOsJHB8BgHQTDm7u0oTVU0ZHlKH2-o11RKK7Mz0mLqMy2EPdkGY9Bqtj5LZ8oTp8FaVqY1g5Fr5veXYpOMbc2fke-e2hG8sAfSjWz1Mq9BUhJ7HdK7TTIte12pub2nbUs4APYystJWx49cYmUwZ-5c9X295V-NX9UksuMSzFItZ4cACVKi68m9lkR4RuNQKFTuLvWsorz9yRx884e4cnoT_JmfSfYBIl31FfnQzUtCjluUzuD9jVXc_vgC7num_0AreOZiUzpglb8UjKXjswTHF-v_nEIaq7YmM5WKpeg

The header and pay­load sec­tions con­tain in­for­ma­tion as a JSON ob­ject, whereas the sig­na­ture is just a plain string. JSON ob­jects con­tain key value pairs which are called claims.

The claims in­for­ma­tion can be ver­i­fied and trusted be­cause it is dig­i­tally signed with the pri­vate key from a pub­lic/pri­vate key-​pair. The sig­na­ture can later be ver­i­fied with a cor­re­spond­ing pub­lic key. The iden­ti­fier of the pub­lic/pri­vate key-​pair used to sign a JWT can be con­tained in a spe­cial claim called kid (key iden­ti­fier) in the header sec­tion of the JWT.

An ex­am­ple for a JWT header that ref­er­ences a pub­lic/pri­vate key-​pair looks like this:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "c7M1_hdZ0-X3re9uvfKHTCQdqarPbpLnUI0ymvAtSTs"
}

It is quite com­mon to use JWTs to con­vey in­for­ma­tion about au­then­ti­ca­tion (user iden­tity) and au­tho­riza­tion (scopes, user roles, per­mis­sions and other claims). OpenID providers such as Key­cloak sup­port is­su­ing OAuth2 ac­cess to­kens after au­then­ti­ca­tion for users to clients in the form of JWTs. An ac­cess token can then be used to ac­cess other ser­vices or APIs on be­half of the user. The server pro­vid­ing those ser­vices or APIs is often called resource server.

An ex­am­ple JWT pay­load gen­er­ated by Key­cloak looks like this:

{
  "exp": 1601318422,
  "iat": 1601318122,
  "jti": "7635f5a1-f1d7-457d-b869-ad492352f4d2",
  "iss": "http://localhost:8080/auth/realms/vertx",
  "aud": "account",
  "sub": "27b3fac0-9aec-4421-81cf-d4b02428f901",
  "typ": "Bearer",
  "azp": "vertx-service",
  "session_state": "68708213-40b7-458a-aee1-39d6f9da07ad",
  "acr": "1",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization",
      "user"
    ]
  },
  "scope": "email profile",
  "email_verified": true,
  "name": "Theo Tester",
  "preferred_username": "tester",
  "given_name": "Theo",
  "family_name": "Tester",
  "email": "tom+tester@localhost"
}

If a resource server re­ceives a re­quest with such an ac­cess token, it needs to ver­ify and in­spect the token be­fore it can trust its con­tent. To ver­ify the token, the resource server needs to ob­tain the public key to check the token sig­na­ture. This public key can ei­ther be con­fig­ured sta­t­i­cally or fetched dy­nam­i­cally from the OpenID Provider by lever­ag­ing the kid in­for­ma­tion from the JWT header sec­tion. Note that most OpenID providers, such as Key­cloak, pro­vide a ded­i­cated end­point for dy­namic pub­lic key lookups, e.g. http://localhost:8080/auth/realms/vertx/protocol/openid-connect/certs. A stan­dard for pro­vid­ing pub­lic key in­for­ma­tion is JSON Web Key Set (JWKS). The JWKS in­for­ma­tion is usu­ally cached by the re­source server to avoid the over­head of fetch­ing JWKS for every re­quest.

An ex­am­ple re­sponse for Key­cloak’s JWKS end­point looks like this:

{
   "keys":[
      {
         "kid":"c7M1_hdZ0-X3re9uvfKHTCQdqarPbpLnUI0ymvAtSTs",
         "kty":"RSA",
         "alg":"RS256",
         "use":"sig",
         "n":"iFuX2bAXA99Yrv6YEvpV9tjS52krP5UJ7lFL02Zl83PPV6PiLIWKTqF71bfTKnVDxO421xAsBw9f6dlgoyxxY1H_bzJQQryQkry7DA7tI_SnKVsehLgeF-tCcjRF_MF1kM14F1A5Zsu6oYIkMZvgJIRM-ejtz3aUcdnLcTvpPrmfvj7KwRgNsfm6Q-kO0-OAf6m6LaRvaC5VpTIRoVxXNhSIiGKuZ4d05Yk0-HdOR0D0sfOujYzleJmTGBEIAmdWpZqUXiSWbzmpw8mJmacFTP9v8lsTUYZrXc69xm5fHaNJ6PO_E-IKiPKT7OeoM2l3HIK76a4azVL1Ewbv1UtMFw",
         "e":"AQAB",
         "x5c":[
            "MIICmTCCAYECBgFwplKOujANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQDDAV2ZXJ0eDAeFw0yMDAzMDQxNjExMzNaFw0zMDAzMDQxNjEzMTNaMBAxDjAMBgNVBAMMBXZlcnR4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiFuX2bAXA99Yrv6YEvpV9tjS52krP5UJ7lFL02Zl83PPV6PiLIWKTqF71bfTKnVDxO421xAsBw9f6dlgoyxxY1H/bzJQQryQkry7DA7tI/SnKVsehLgeF+tCcjRF/MF1kM14F1A5Zsu6oYIkMZvgJIRM+ejtz3aUcdnLcTvpPrmfvj7KwRgNsfm6Q+kO0+OAf6m6LaRvaC5VpTIRoVxXNhSIiGKuZ4d05Yk0+HdOR0D0sfOujYzleJmTGBEIAmdWpZqUXiSWbzmpw8mJmacFTP9v8lsTUYZrXc69xm5fHaNJ6PO/E+IKiPKT7OeoM2l3HIK76a4azVL1Ewbv1UtMFwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBxcXiTtGoo4/eMNwhagYH8QpK1n7fxgzn4mkESU3wD+rnPOAh/xFmx5c3aq8X+8W2z7oopO86ZBSQ8HfbzViBP0uwvf7s7E6Q8FOqrUNv0Kj308A7hF1IOqOhCJE2nABIWJduYz5dWZN434Q9El30L1eOYTtjBUmCdP7/CM+1bvxIT+CYrWmjI9zCMJxhuixmLffppsLCjGtNgFBemjQyCrLxpEGCfy8QGb4pTY/XaHuJ7k6ZaQkVeTbeDzaZbHc9zT5qgf6w4Gp7y+uPZdAsasrwiqm3YBtyBfaK42luk09nHpV6PRKpftnyLVPwlQiJAW6ZMckvDwmnDst70msnb"
         ],
         "x5t":"MVYTXCx5cUQ8lT1ymIDDRYO7_ZI",
         "x5t#S256":"yBDVTlfR0e7cv3HxbbkfvGKVs5W1VQtFs7haE_js3DY"
      }
   ]
}

The keys array con­tains the JWKS struc­ture with the pub­lic key in­for­ma­tion that be­longs to the pub­lic/pri­vate key-​pair which was used to sign the JWT ac­cess token from above. Note the match­ing kid claim from our ear­lier JWT header ex­am­ple.

Now that we have the ap­pro­pri­ate pub­lic key, we can use the in­for­ma­tion from the JWT header to val­i­date the sig­na­ture of the JWT ac­cess token. If the sig­na­ture is valid, we can go on and check ad­di­tional claims from the pay­load sec­tion of the JWT, such as ex­pi­ra­tion, al­lowed is­suer and au­di­ence etc.

Now that we have the nec­es­sary build­ing blocks in place, we can fi­nally look at how to con­fig­ure JWT au­tho­riza­tion in Vert.x.

JWT Authorization in Vert.x

Set­ting up JWT au­tho­riza­tion in Vert.x is quite easy. First we need to add the vertx-auth-jwt mod­ule as a de­pen­dency to our project.

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-auth-jwt</artifactId>
</dependency>

In our ex­am­ple, the whole JWT au­tho­riza­tion setup hap­pens in the method setupJwtAuth.

We use a WebClient to dy­nam­i­cally fetch the pub­lic key in­for­ma­tion from the /protocol/openid-connect/certs JWKS end­point rel­a­tive to our Key­cloak is­suer URL. After that, we con­fig­ure a JWTAuth in­stance and cus­tomize the JWT val­i­da­tion via JWTOptions and JWTAuthOptions. Note that we use Key­cloak’s realm roles for role based au­tho­riza­tion via the JWTAuthOptions#setPermissionsClaimKey(..) method.

private Future<Startup> setupJwtAuth(Startup startup) {

    var jwtConfig = startup.config.getJsonObject("jwt");
    var issuer = jwtConfig.getString("issuer");
    var issuerUri = URI.create(issuer);

    // derive JWKS uri from Keycloak issuer URI
    var jwksUri = URI.create(jwtConfig.getString("jwksUri", String.format("%s://%s:%d%s",
            issuerUri.getScheme(), issuerUri.getHost(), issuerUri.getPort(), issuerUri.getPath() + "/protocol/openid-connect/certs")));

    var promise = Promise.<JWTAuth>promise();

    // fetch JWKS from `/certs` endpoint
    webClient.get(jwksUri.getPort(), jwksUri.getHost(), jwksUri.getPath())
            .as(BodyCodec.jsonObject())
            .send(ar -> {

                if (!ar.succeeded()) {
                    startup.bootstrap.fail(String.format("Could not fetch JWKS from URI: %s", jwksUri));
                    return;
                }

                var response = ar.result();

                var jwksResponse = response.body();
                var keys = jwksResponse.getJsonArray("keys");

                // Configure JWT validation options
                var jwtOptions = new JWTOptions();
                jwtOptions.setIssuer(issuer);

                // extract JWKS from keys array
                var jwks = ((List<Object>) keys.getList()).stream()
                        .map(o -> new JsonObject((Map<String, Object>) o))
                        .collect(Collectors.toList());

                // configure JWTAuth
                var jwtAuthOptions = new JWTAuthOptions();
                jwtAuthOptions.setJwks(jwks);
                jwtAuthOptions.setJWTOptions(jwtOptions);
                jwtAuthOptions.setPermissionsClaimKey(jwtConfig.getString("permissionClaimsKey", "realm_access/roles"));

                JWTAuth jwtAuth = JWTAuth.create(vertx, jwtAuthOptions);
                promise.complete(jwtAuth);
            });

    return promise.future().compose(auth -> {
        jwtAuth = auth;
        return Future.succeededFuture(startup);
    });
}

Protecting routes with JWTAuthHandler

Now that our JWTAuth is con­fig­ured, we can use the JWTAuthHandler in the setupRouter method to apply JWT au­tho­riza­tion to all routes match­ing the path pat­tern /api/*. The JWTAuthHandler val­i­dates re­ceived JWTs and per­forms ad­di­tional checks like ex­pi­ra­tion and al­lowed is­suers. With that in place, we con­fig­ure our ac­tual routes in setupRoutes.

private Future<Startup> setupRouter(Startup startup) {

    router = Router.router(vertx);

    router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth));

    return Future.succeededFuture(startup);
}

private Future<Startup> setupRoutes(Startup startup) {

    router.get("/api/greet").handler(this::handleGreet);
    router.get("/api/user").handler(this::handleUserData);
    router.get("/api/admin").handler(this::handleAdminData);

    return Future.succeededFuture(startup);
}

Extracting user information from JWTUser

To ac­cess user in­for­ma­tion in our handleGreet method, we cast the re­sult of the io.vertx.ext.web.RoutingContext#user method to JWTUser which al­lows us to ac­cess token claim in­for­ma­tion via the io.vertx.ext.auth.jwt.impl.JWTUser#principal JSON ob­ject.

If we’d like to use the JWT ac­cess token for other ser­vice calls, we could ex­tract the token from the Authorization header.

private void handleGreet(RoutingContext ctx) {

    var jwtUser = (JWTUser) ctx.user();
    var username = jwtUser.principal().getString("preferred_username");
    var userId = jwtUser.principal().getString("sub");

    var accessToken = ctx.request().getHeader(HttpHeaders.AUTHORIZATION).substring("Bearer ".length());
    // Use accessToken for down-stream calls if needed...

    ctx.request().response().end(String.format("Hi %s (%s) %s%n", username, userId, Instant.now()));
}

Obtaining an Access Token from Keycloak for user tester

To test our ap­pli­ca­tion we can use the fol­low­ing curl com­mands in a bash like shell to ob­tain an JWT ac­cess token to call one of our end­points as the user tester with the role user.

Note that this ex­am­ple uses the cli tool jq for JSON pro­cess­ing.

KC_USERNAME=tester
KC_PASSWORD=test
KC_CLIENT=vertx-service
KC_CLIENT_SECRET=ecb85cc5-f90d-4a03-8fac-24dcde57f40c
KC_REALM=vertx
KC_URL=http://localhost:8080/auth
KC_RESPONSE=$(curl  -k \
        -d "username=$KC_USERNAME" \
        -d "password=$KC_PASSWORD" \
        -d 'grant_type=password' \
        -d "client_id=$KC_CLIENT" \
        -d "client_secret=$KC_CLIENT_SECRET" \
        "$KC_URL/realms/$KC_REALM/protocol/openid-connect/token" \
    | jq .)

KC_ACCESS_TOKEN=$(echo $KC_RESPONSE| jq -r .access_token)
echo $KC_ACCESS_TOKEN

Here we use the JWT ac­cess token in the Authorization header with the Bearer pre­fix to call our greet route:

curl --silent -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://localhost:3000/api/greet

Ex­am­ple out­put:

Hi tester (27b3fac0-9aec-4421-81cf-d4b02428f901) 2020-09-28T21:03:59.254230700Z

Applying Role-based Access-Control with JWTUser

To lever­age sup­port for role based ac­cess con­trol (RBAC) we can use the io.vertx.ext.auth.User#isAuthorised method to check whether the cur­rent user has the re­quired role. If the role is present we re­turn some data about the user, oth­er­wise we send a re­sponse with sta­tus code 403 and a forbidden error mes­sage.

private void handleUserData(RoutingContext ctx) {

    var jwtUser = (JWTUser) ctx.user();
    var username = jwtUser.principal().getString("preferred_username");
    var userId = jwtUser.principal().getString("sub");

    jwtUser.isAuthorized("user", res -> {

        if (!res.succeeded() || !res.result()) {
            toJsonResponse(ctx).setStatusCode(403).end("{\"error\": \"forbidden\"}");
            return;
        }

        JsonObject data = new JsonObject()
                .put("type", "user")
                .put("username", username)
                .put("userId", userId)
                .put("timestamp", Instant.now());

        toJsonResponse(ctx).end(data.toString());
    });
}

private void handleAdminData(RoutingContext ctx) {

    var jwtUser = (JWTUser) ctx.user();
    var username = jwtUser.principal().getString("preferred_username");
    var userId = jwtUser.principal().getString("sub");

    jwtUser.isAuthorized("admin", res -> {

        if (!res.succeeded() || !res.result()) {
            toJsonResponse(ctx).setStatusCode(403).end("{\"error\": \"forbidden\"}");
            return;
        }

        JsonObject data = new JsonObject()
                .put("type", "admin")
                .put("username", username)
                .put("userId", userId)
                .put("timestamp", Instant.now());

        toJsonResponse(ctx).end(data.toString());
    });
}
curl --silent -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://localhost:3000/api/user

Out­put:

{"type":"user","username":"tester","userId":"27b3fac0-9aec-4421-81cf-d4b02428f901","timestamp":"2020-09-28T21:07:49.340950300Z"}
curl --silent -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://localhost:3000/api/admin

Out­put:

{"error": "forbidden"}

Obtaining an Access Token from Keycloak for user vadmin

To check ac­cess with an admin role, we ob­tain a new token for the user vadmin which has the roles admin and user.

KC_USERNAME=vadmin
KC_PASSWORD=test
KC_CLIENT=vertx-service
KC_CLIENT_SECRET=ecb85cc5-f90d-4a03-8fac-24dcde57f40c
KC_REALM=vertx
KC_URL=http://localhost:8080/auth
KC_RESPONSE=$(curl  -k \
        -d "username=$KC_USERNAME" \
        -d "password=$KC_PASSWORD" \
        -d 'grant_type=password' \
        -d "client_id=$KC_CLIENT" \
        -d "client_secret=$KC_CLIENT_SECRET" \
        "$KC_URL/realms/$KC_REALM/protocol/openid-connect/token" \
    | jq .)

KC_ACCESS_TOKEN=$(echo $KC_RESPONSE| jq -r .access_token)
echo $KC_ACCESS_TOKEN
curl --silent -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://localhost:3000/api/user

Out­put:

{"type":"user","username":"vadmin","userId":"75090eac-36ff-4cd8-847d-fc2941bc024e","timestamp":"2020-09-28T21:13:05.099393900Z"}
curl --silent -H "Authorization: Bearer $KC_ACCESS_TOKEN" http://localhost:3000/api/admin

Out­put:

{"type":"admin","username":"vadmin","userId":"75090eac-36ff-4cd8-847d-fc2941bc024e","timestamp":"2020-09-28T21:13:34.945276500Z"}

Conclusion

We learned how to con­fig­ure a Vert.x ap­pli­ca­tion with JWT au­tho­riza­tion pow­ered by Key­cloak. Al­though the con­fig­u­ra­tion is quite com­plete al­ready, there are still some parts that can be im­proved, like the dy­namic JWKS fetch­ing on public-​key pair ro­ta­tion as well as ex­trac­tion of nested roles.

Nev­er­the­less this is a good start­ing point for se­cur­ing your own Vert.x ser­vices with JWT and Key­cloak.

You can check out the com­plete ex­am­ple in keycloak-​vertx Ex­am­ples Repo.

Thank you for your time, stay tuned for more up­dates! If you want to learn more about Key­cloak, feel free to reach out to me. You can find me via thomas­da­ri­mont on twit­ter.

Happy Hack­ing!

Next post

Eclipse Vert.x 3.9.4 released!

Eclipse Vert.x version 3.9.4 has just been released. It fixes quite a few bugs that have been reported by the community.

Read more
Previous post

Eclipse Vert.x 4 beta 3 released!

The third beta version of Eclipse Vert.x 4.0 highlights many features including (but not limited to) HTTP client request creation, HTTP tunnel improvements, and WebSocket upgrade improvements.

Read more
Related posts

Easy SSO for Vert.x with Keycloak

In this blog post, you'll learn how to implement Single Sign-on with OpenID Connect and how to use Keycloak together with Eclipse Vert.x.

Read more

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more

Combine vert.x and mongo to build a giant

This blog post is part of the introduction to Vert.x series. We are now going to replace this JDBC client by the vertx-mongo-client, and thus connect to a Mongo database.

Read more