Keycloak auth for REST backend with JWT

2,840 views
Skip to first unread message

Paul Bakker

unread,
May 3, 2016, 4:03:42 PM5/3/16
to vert.x
Hi all,

I have a setup with a JS frontend, that talks to a Vert.x REST backend.
The frontend is secured using Keycloak's JS library, which redirects to the login page when necessary.
The backend code in Vert.x should only be accessible for authenticated users, and information about the user's groups etc. need to be available to Vert.x as well.

What is the correct way to set this up? The Keycloak/Vert.x docs and tutorial are about setting up redirects for accessing a secured page. For RESTful webservices, redirects aren't useful. The only thing that needs to happen is verify the JWT token and parse information form the token.
The Vertx Oauth API doesn't seem to have methods for this (which is understandable, because it's more OIDC than Oauth). 

Something like the following works, but this seems more low level than I would expect:

m_router.route().order(-1).handler(req -> {


 String authHeader = req.request().getHeader("Authorization");

String jwt = authHeader.substring(authHeader.indexOf(" ") + 1);

 TokenVerifier tokenVerifier = new TokenVerifier("public_key");


 JsonObject verify = tokenVerifier.verify(jwt);

System.out.println(verify);


               
  req.next();

});



I have also tried getting the Vertx JWT plugin to work with the JWT keys generated by Keycloak, but keep getting "JWT decode failure" errors. 

Regards,

Paul

Paulo Lopes

unread,
May 4, 2016, 9:41:59 AM5/4/16
to vert.x
The AuthProvider implementation contains a method: isAuthorised(String grant) you can use that to query the JWT or even the Keycloak token for the grants you need e.g.:

oauth2.getToken(new JsonObject().put("username", "pmlopes").put("password", "password"), res -> {
      if (res.failed()) {
        fail(res.cause().getMessage());
      } else {
        AccessToken token = res.result();
        assertNotNull(token);
        assertNotNull(token.principal());

        token.isAuthorised("account:manage-account", r -> {
          assertTrue(r.result());

...

Arnold Schrijver

unread,
May 4, 2016, 12:23:07 PM5/4/16
to vert.x
Hi Paul,

I have written a small library for decoding and encoding of JWT tokens for Vert.x that is separate from OAuth and allows you more control of various settings.
This is an example of how it is called:

    @Test
   
public void should_Decode_Json_Web_Token(TestContext context) throws Exception {
       
final Async async = context.async();
       
JsonWebToken.decode(JWT_VALID, DecoderSettings.decode().withSecret(SECRET), result -> {
            assertTrue
(result.succeeded());

           
JsonWebToken token = result.result();
            assertNotNull
(token);

           
Claims claims = token.claims();
            assertEquals
(3, claims.getClaims().size());
            assertEquals
("joe", claims.issuer());
            assertEquals
(1300819380L, claims.expiration());
            assertTrue
(claims.getClaim("http://example.com/is_root"));
            assertEquals
(1, claims.getCustomClaims().size());

            async
.complete();
       
});
   
}

Is this something that might help you?

I am intending to put this on Github as vertx-jwt-codec one of these days, but did not find the time yet. If you are interested I may put it up sooner even though it is still a bit rough 'round the edges.

Regards,

Arnold.



On Tuesday, May 3, 2016 at 10:03:42 PM UTC+2, Paul Bakker wrote:

Paul Bakker

unread,
May 4, 2016, 12:56:45 PM5/4/16
to vert.x
Hi Paulo,

I'm still not getting it. The frontend is already sending me a token. JWT tokens are self contained, so I shouldn't have to ask for another token I think. So how would I do the same based on the token sent from the frontend?

I ended up with the following code btw, which actually seems reasonably easy. So maybe the correct answer to my question is to "just do it yourself".

m_router.route().order(-1).handler(req -> {
 
String authHeader = req.request().getHeader("Authorization");

 
if(authHeader != null) {

   
String jwt = authHeader.substring(authHeader.indexOf(" ") + 1);
   
TokenVerifier tokenVerifier = new TokenVerifier("public_key");
 JsonObject verify = tokenVerifier.verify(jwt);

 

 req
.setUser(new User(new CloudRtiUser(verify)));

 

 req
.next();

 
} else {

 req
.response().setStatusCode(403).end();

 
}

 
});

Paul Bakker

unread,
May 4, 2016, 1:44:44 PM5/4/16
to vert.x
Looks helpful! If you're publishing, don't forget OSGi metadata :-)

Paulo Lopes

unread,
May 4, 2016, 3:26:34 PM5/4/16
to vert.x
If all you're looking for is JWT support (not really keycloak) you can just use the JWT auth handler

    JWTAuth authProvider = JWTAuth.create(vertx, authConfig);

    router.route("/protected/*").handler(JWTAuthHandler.create(authProvider));

by using this the auth handler will ensure that all request under /protected/* have a valid Authorization header and that the token is valid (both signature and validity)

and later in your handler you can do assertions on grants as simple as:

    rc.user().isAuthorised("account:manage-account", manageAccount -> {
        if (manageAccount.result()) {
          rc.response().end("Welcome to the protected resource and you can manage your account!");
        } else {
          rc.response().end("Welcome to the protected resource but you cannot manage your account!");

Filipe Delgado

unread,
May 18, 2016, 11:47:09 AM5/18/16
to vert.x
Hi Paul.

How do you verify the RSA signed token agains the realm public key???

Paul Bakker

unread,
May 18, 2016, 11:49:43 AM5/18/16
to vert.x
Hi Filipe,

I'm using the code below:

String jwt = authHeader.substring(authHeader.indexOf(" ") + 1); 
TokenVerifier tokenVerifier = new TokenVerifier("public_key"); 
JsonObject verify = tokenVerifier.verify(jwt); 

Cheers,

Paul
Reply all
Reply to author
Forward
0 new messages