Errai: The browser as a platform

Monday, January 19, 2015

Errai 3.1.1.Final released!

Hi everyone,

Just a quick note that we released Errai 3.1.1.Final today. This is a maintenance release containing a few bug fixes (see the release notes for details). We have also switched master to 3.2.0-SNAPSHOT for future development. So far 3.2 contains a new feature that allows for server-side (dynamic) Errai UI templates. Please stay tuned for more information and an updated roadmap.

Everything is set for GWT.create now and I hope to see many of you in Mountain View and Munich in the coming days!

Happy coding!

Wednesday, January 14, 2015

SSO with Errai and Keycloak.js

The Artificer project (http://artificer.jboss.org) has been a long-time fan of Errai, relying on it for the majority of our powerful UI. We recently switched to using Keycloak, an out-of-the-box security solution, for authentication and authorization. Typically, you'd protect your Errai index.html page with a simple security-constraint in the WAR's web.xml.  However, due to various issues, we investigated the use of Keycloak's pure-Javascript adapter (keycloak.js). This post will quickly capture the approach and a few gotchas.

Note that Errai has a built-in Keycloak module, based on Errai Security. Although that may be the best way to go, we were interested in making keycloak.js work on its own with plain Errai.

index.html

So, first things first.  When you run Keycloak, either using its standalone appliance or within a container of your choice, it serves up the keycloak.js file.  Add it to your page's header:


Right off the bat, there's a few things to notice:

  • The keycloak variable is attached to the window object.  More on that later...
  • The init call includes the {onLoad: 'login-required'} option. This tells Keycloak to redirect users to the built-in login screen, if un-authenticated.  If left off, the user must be pre-authenticated by other means.
  • Keycloak revolves around the use of tokens. Server-side, those tokens expire after a configurable period.  For Javascript-based solutions, such as Errai, that may never refresh the page, manually refreshing the token is vital. However, Keycloak's updateToken is currently asynchronous and uses a promise. Rather than deal with a complicated callback setup, we're simply updating the token on a 10 second timer.  By default, Keycloak tokens expire after a minute, however this value is configurable.

REST

The back-end REST services are also protected by Keyloak.  In order to use them, without requiring basic authentication, the Errai app must provide a "bearer token" on each call.  To accomplish this, we created a RestClientInterceptor to intercept each call and add the token.


Here, we use a bit of JSNI to obtain the Keycloak token from index.html.  Here's where the window object necessity comes in.  Since this interceptor technically runs in a nested frame, simple global variables will not work.  Attaching the variable to window, then referring to it through JSNI's $wnd, allows the cross-frame sharing.

After obtaining the token, the interceptor adds it to the request.  Note that the header key is "Authorization" and the value is "Bearer [token]".  The "Bearer" bit is vital, but can be easily forgotten.

RPC

The above approach works similarly for RPC, but is instead based on RpcInterceptor.

Servlets

What about Servlets?  In our case, Servlets are used directly from GWT FormPanel for needs such as local file uploads.  The Servlets themselves are not protected by Keycloak, but they turn around and call the same REST services described above.  So, the bearer token is still needed, but in a proxied manner. Since there's not a request originating from Errai to intercept, we have to use this workaround.


We automatically add a Hidden input, called "Authorization", to the form.  When the form is submitted, we're re-using the JSNI in KeycloakBearerTokenInterceptor to set the input's value to "Bearer [token]". Then, in the Servlet's doPost, the "Authorization" parameter is pulled from the HttpServletRequest. The token is then used on the proceeding call to the REST services.

One caveat that's often overlooked: if your form sends multipart data, such as our file upload, the "Authorization" parameter is a part of the request's file/form items, not in request.getParameter("Authorization").

For GET, it's obviously easier. Simply add the "Authorization" parameter, identical to the above (if in a form) or by adding it to the URL query string. The Servlet grabs it through request.getParameter("Authorization").