Building a WebAuthn Application with Java

The Web Authentication (WebAuthn) specification, given official approval by the World Wide Web Consortium (W3C) and the FIDO Alliance in 2019, aims to strengthen online security by allowing users to sign in to sites with elements like biometrics and FIDO security keys. The WebAuthn API can replace or supplement less-secure passwords, which may be weak and are often shared.


This is a companion discussion topic for the original entry at https://developer.okta.com/blog/2022/04/26/webauthn-java

Hi, thank you for the blog and explaining the process.

I am trying to use postgres instead of H2 database, but getting errors. the registration is done successfully, but when trying to login I get the following error.
Please Advise on what I need to do to use Postgres or MySQL database.

ERROR 4087 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Authentication failed] with root cause

java.lang.IllegalArgumentException: Unknown credential: ByteArray(daa5ee8faf432ce816fc9edb79e195cc61bf56ab9661b832ec7bfb558e832981)
	at com.yubico.internal.util.ExceptionUtil.assure(ExceptionUtil.java:42) ~[yubico-util-2.2.0-RC1.jar:2.2.0-RC1]
	at com.yubico.webauthn.FinishAssertionSteps$Step6.validate(FinishAssertionSteps.java:176) ~[webauthn-server-core-2.2.0-RC1.jar:2.2.0-RC1]
	at com.yubico.webauthn.FinishAssertionSteps$Step.next(FinishAssertionSteps.java:90) ~[webauthn-server-core-2.2.0-RC1.jar:2.2.0-RC1]
	at com.yubico.webauthn.FinishAssertionSteps$Step.run(FinishAssertionSteps.java:98) ~[webauthn-server-core-2.2.0-RC1.jar:2.2.0-RC1]
	at com.yubico.webauthn.FinishAssertionSteps$Step.run(FinishAssertionSteps.java:98) ~[webauthn-server-core-2.2.0-RC1.jar:2.2.0-RC1]
	at com.yubico.webauthn.FinishAssertionSteps.run(FinishAssertionSteps.java:77) ~[webauthn-server-core-2.2.0-RC1.jar:2.2.0-RC1]
	at com.yubico.webauthn.RelyingParty.finishAssertion(RelyingParty.java:576) ~[webauthn-server-core-2.2.0-RC1.jar:2.2.0-RC1]
	at com.captech.passkey.backend.controller.AuthController.finishLogin(AuthController.java:174) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:696) ~[tomcat-embed-core-9.0.68.jar:4.0.FR]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:779) ~[tomcat-embed-core-9.0.68.jar:4.0.FR]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.68.jar:9.0.68]
	at java.base/java.lang.Thread.run(Thread.java:831) ~[na:na]

Is the URL that registered the key the same as the one used for challenging? (for example, you cannot use both localhost and 127.0.0.1).

That’s just a guess, though.

Yes, I use the same URL. It works fine when H2 database is being used. I believe the issue comes from ByteArray and how it is getting saved to database. I have used different types, but it seems to be only save as a BigInt.
I wanted to see if anyone got it working with any non-memory based database?

Sorry @aminasemani! I missed the “it only happens with Postgres bit”.

I’m guessing the conversion between to/from the ByteArray is the problem.

For a quick hack to verify that’s the problem, you could Base64 encode the value before storing it in Postgres, then decode it on the way out. If that fixes the issue, you could dig into the actual byte storage problem.

@bdemers . I tried different Base64 conversion but getting the below error. what other database do you think it will work with the webauthn library?

java.lang.IllegalArgumentException: Parameter value [ByteArray(713649e5b05a522cc8e3a5b033c37e805a8d7a54bb790ac7e14a9a81eded8ce9)] did not match expected type [[B (n/a)]
	at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.6.12.Final.jar:5.6.12.Final]
	at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:27) ~[hibernate-core-5.6.12.Final.jar:5.6.12.Final]
	at org.hibernate.query.internal.QueryParameterBindingImpl.validate(QueryParameterBindingImpl.java:90) ~[hibernate-core-5.6.12.Final.jar:5.6.12.Final]

Make sure you tear down the database (drop the tables) before changing the field/column types.

Hibernate should then recreate the column as a VARCHAR, and that should work!

Keep us posted!

I have tried multiple different column types such OID, VARCHAR, BIGINT, … . still seeing the same error.

Did you guys try it on any database other than H2 DB? I am willing to use another DB if that works.

The example was only tested with H2.

Do you have a fork/branch with your base64 and VARCHAR changes?
(ultimately, you shouldn’t need to base64 the value, but different DBs may handle binary data differently)

@bdemers thank you for the help. I was able to get it working by converting all the ByteArray variables to string and saving them as varchar in database. Also needed to remove @lob annotation.

1 Like

hi @aminasemani @bdemers do you know why i get the error "ServerError: [object ReadableStream] when I have different Username Display name and CredentialName. Why it should be the same and how i can implement that the username display name and credentialanme can be different?

is it supports to create passkeys?

Hello @bdemers, Thanks for the article its really helpful. I have tried web Authentication application by using java and uses PostgreSQL for DB connectivity. It is working properly on localhost. But when I am trying to test server URL by using my IP address on different machine its giving error like “SecurityError: The relying party ID is not a registrable domain suffix of, nor equal to the current domain.” or “TypeError: Cannot read properties of undefined (reading ‘create’)” .

FYI , I have update authn.hostname and authn.authOrigin in application.properties file and gives my server IP address.

Please guides me on above.

Thank you in advance.!