Example app: WebAuthn/Passkeys on top of Spring Security

Pretty much a year ago, I wrote an ode to WebAuthn and Passkeys. This year, we have Spring Security support for these technologies, and I wanted to give it a try.

I started with a “normal Vaadin app stub” from start.vaadin.com, specifically one with “form-based authentication using users saved in a DB with JPA,” built with Spring Security. On top of that, I created an example path to show how you can get rid of your current username-password authentication in your existing app :sunglasses:

The solution uses standard Spring Security components and some Vaadin “optimisations”. For example, the passkey registration only utilizes the server-side APIs and some of the JS utilities, bypassing the default HTML pages and HTTP request handling provided by Spring Security filters. It should be pretty much production-ready, although you need to configure the PublicKeyCredentialRpEntity for your domain/app (and maybe provide a different version for development).

Since Spring Security’s solution seems to require that passkeys can only be registered when already logged in, you’ll likely need an alternative authentication method—even if you plan to completely ditch username-password logins. Partly for this reason, my first step was to add OTT (aka “forgot your password link”) based login.

I can’t say I’m entirely in love with all the API design decisions of Spring Security/WebAuthn4J (like introducing the cumbersome Bytes instead of just using raw byte[]). The official docs are also quite limited (not mentioning what you really need for a production-ready solution), but hey, at least it’s proven to work now :muscle:. It would be hard to avoid using this existing infrastructure if you’re already using Spring Security anyway.

I’ll write some article(s) on the subject at some point, but for now, the commit timeline is probably decent documentation—at least if you already know something about passkeys & Spring Security.

For the example itself, I might still configure that one OTT page to be Vaadin-native, now it has “different theme” that the rest of the app which is Vaadin & Lumo theme. I might also create a branch where the passkey authentication happens more in a Vaadin/SPA manner rather than relying on Spring Security’s filter (now the Vaadin page initiates the login, but JS makes post request and then redirects). That would provide smoother UX and require less custom Spring Security config (e.g., CSRF prevention is disabled for WebAuthn login requests so they can be directly made from a Vaadin-based login form. Shouldn’t affect security much since there’s not much to steal for a non-authenticated user :man_shrugging:).

Check it out and share your thoughts! I also plan to create an example that uses EclipseStore for persistence—I’d expect that to be much easier since, for me, the hardest part here was persisting the CredentialRecord(s) (and their Bytes) with JPA and properly configuring Postgres/Hibernate for byte[].

7 Likes

Hi, wondering if you can help. I’ve downloaded the Passkey demo application, but having trouble getting it to run.

Following the instructions, if I debug run the Test Application I get the following output:

Connected to the target VM, address: ‘127.0.0.1:57790’, transport: ‘socket’
Exception ‘java.lang.NoSuchFieldException’ occurred in thread ‘main’ at java.lang.Class.getField(Class.java:2068)
Breakpoint reached
at java.lang.Class.getField(Class.java:2068)
at com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory.

Disconnected from the target VM, address: ‘127.0.0.1:57790’, transport: ‘socket’

Process finished with exit code -1073741819 (0xC0000005)

I have Docker desktop running.

If I try Spring-Boot run on main application, it doesn’t build and requests a DataSource, so I placed embedded H2 in the pom. It now builds and runs at localhost:8080. However if I try and sign in with user or admin as instructed I get redirected to login?error.

Any thoughts on what I’m missing?

Thanks for your help.

Did you run this applicaiton class? It uses Testcontainers, so you need to run custom main method from src/test instead of the basic Applicaiton class. pathtopasskeys/src/test/java/com/example/application/TestApplication.java at main · mstahv/pathtopasskeys · GitHub

Alternatively, “mvn spring-boot:test-run” should do it.

Good that you remineded about that BTW. I should update that to latest versions. There are some changes in both Spring Boot 4 and Vaadin 25 that should get into this piece…

Thanks for your help Matti.

I was running the correct application class , but “mvn spring-boot: test-run” did the trick. There was also a conflict with latest version of Docker engine (29), so I had to set Testcontainers to older version in pom properties to make it work, i.e.

<testcontainers.version>1.21.4</testcontainers.version>

Otherwise all working - thanks for the demo!

Excellent, I’ll still keep updating that example on my TODO, I assume you also want to run on latest Vaadin and Spring Boot :teddy_bear: If you get on upgrading the demo, feel free to drop a PR in case you get it updated!

Happy to take up the challenge. I’ve recently migrated my application to Vaadin 25/ Spring Boot 4, so will try the same with your demo.

I’ve submitted a pull request for the passkey demo migration to Vaadin 25 and Spring 4. As per the description, seems to all work except for the Entityexplorer which needs migration as well.

2 Likes

Awesome, I’ll look into it today!