JWT (JSON Web Token) is a popular way to handle authentication in stateless, browser-centric web UIs. Vaadin Flow runs its UI state and logic in the server memory – by design. Without that, Flow users couldn’t have the pure Java developer experience and simplicity they love.
At first glance, it might feel like there is no sense in utilizing stateless JWT authentication in a stateful web application like Vaadin, but in certain cases it might actually make a lot of sense in order to enhance the experience in some special use cases.
Advantages of stateless authentication
So why would you use JWT, as there will always be the good old Java Servlet Session? Session is the traditional, dead-simple and battle-proven way to store the authentication details in Java web apps. The primary reason is that the authentication state is something that you rarely want to discard, even if you would really like to reset the other parts of your application state. Some applications have some kind of “remember me” option, but that option isn’t available to all.
Better DX: Stay logged in during development
The place where you, as a Vaadin developer, will value JWT-style authentication is during development. I’d claim that around 95% of Vaadin applications have some sort of authentication system in place. And I’d also claim that 95% of those don’t have serializable sessions. When you make a change to your application and want to see your changes, you most often lose your Java session, including your authentication state. Even though deep linking probably keeps you kind of on the right screen, you’ll end up on the login screen. Unless you are developing the login screen – not very handy.
The above scenario is often mitigated by having a special development-time profile that bypasses the authentication altogether. But with JWT, that becomes pretty much obsolete. Since, in JWT, the authentication information is actually stored on the browser side, it won’t get lost, even if the server-side session is discarded. When JRebel reloads the Java class and Vaadin development automatically refreshes the browser, you’ll stay logged in, and on the exact same view that you were on before, only potentially losing filled-in form content and similar – something that you most likely want to discard anyway after UI modification.
Better UX: Keep login on application updates
A slightly related case happens for your end users when you, as a developer, are deploying new versions of your application to certain types of hosting setups. If you don’t tolerate any disruptions in your end-user experience, you are probably running a cluster where you keep nodes with the previous version alive for the currently logged-in users. But if you are running just a single-server hosting setup, like the one I described recently, you’ll probably want to pull the plug from the old sessions at some point to free resources. With traditional session-based authentication, your users will be “kicked out” on this occasion. With JWT, their UI will simply reload and they can continue from where they were.
The same inconvenient re-login might also be required in hosting setups where you let the existing users know that “there is a new version available”. Without stateless authentication, they’ll soon learn that upgrading to a new version also requires them to open up their notebook for the password – again.
Drawbacks of JWT
JWT-based authentication has been battle-tested in many large-scale web apps, so the security compared to traditional session-based security should not be an issue. The drawbacks are rather minimal. You’ll get one additional header/cookie exchanged in each http request, but the payload is almost irrelevant in most cases.
In certain cases, you might get more database queries, as you are no longer storing the full user details in the server memory. If this becomes a problem, you could easily just cache that to your session as well.
Session invalidation is another case where you need to take extra steps, especially if you need to be able to kill users' access at an exact moment. As the session is maintained on the browser side, you can’t just do something like session.invalidate() from the server-side code, which you would probably have done with your-session based approaches. But in an application with high security constraints, you’ll want to check the permissions with every meaningful action anyway, and session.invalidate can be emulated using blacklisting.
Finally, the only notable drawback is a more complex setup. The setup also involves some cryptography, which always causes some security concerns. And to sign the tokens you’ll need a secret key on the server, that must be kept secret. Although implementing JWT yourself is not rocket science, I suggest you rely on some well known library for the implementation!
How to apply in Vaadin apps
To start using JWT in a Vaadin Flow application is technically the same as with other web apps. It is easiest to transfer the token using a cookie (instead of a header), to avoid intercepting (and adding Authorization header) requests that the “client-side engine” of Vaadin makes to the server. JWT-based authentication is not platform-specific, but I don’t suggest that anyone implement it manually.
Using Spring Security, the setup is rather simple. On top of a project generated by start.vaadin.com, which already has access control enabled, we’ll only need two dependencies, one effective line of configuration code and some configuration of the application.properties file.
I prepared a full example project for GitHub. Compared to standard template generated by start.vaadin.com, you'll need a bunch of extra dependencies, enable stateless authentication and a tiny hack to keep logout functional. You'll also need to create a secret key for the JWT to work.
Testing it out
To test the solution and compare it to a normal session-based configuration, you’ll need a Vaadin application whose session doesn't serialize. With default apps produced by start.vaadin.com, you’ll see no difference, but if you have a larger business application already built, there is a good chance that your session is already non-serializable.
For my example application, I made a hack that saves an Object instance to the VaadinSession (and this way finally all the way to the Servlet session). This effectively simulates non-serializable sessions. If you comment out the configuration line in the example, restart the server, log in, and restart again, you should be kicked out of the app. With JWT on, you’ll stay logged in, even if your session is discarded between the restarts.