Java Spring Boot & Passkeys: This tutorial shows how to integrate passkeys into Java Spring Boot apps by implementing passkey-first auth & session management.
Nicolai
Created: September 19, 2023
Updated: March 21, 2025
We aim to make the Internet a safer place using passkeys. That's why we want to support developers with tutorials on how to implement passkeys.
In this blog post, we'll be walking through the process of building a sample application with passkey authentication using Java Spring Boot with Thymeleaf template engine. To make passkeys work, we use Corbado's passkey-first UI component that automatically connects to a passkeys backend.
If you want to see the finished code, please have a look at our sample application GitHub repository.
The result looks as follows:
This tutorial assumes basic familiarity with HTML and Java Spring Boot.
You will also need to install the Corbado Java SDK.
Use latest version available. We will take 0.0.1 as an example.
Add it as a dependency to your project's build/pom
file:
implementation "com.corbado:corbado-java:0.0.1"
Add this dependency to your project's pom.xml
:
pom.xml<dependency> <groupId>com.corbado</groupId> <artifactId>corbado-java</artifactId> <version>0.0.1</version> </dependency>
Let's dive in!
Ben Gould
Head of Engineering
I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.
10,000+ devs trust Corbado & make the Internet safer with passkeys. Got questions? We’ve written 150+ blog posts on passkeys.
Join Passkeys CommunityA Java Spring Boot project contains many files, but the only ones important
for us are in the /complete/src/main folder
. The templates are located under
/resources/templates
while the FrontendController.java
as well as its
JsonReader.java
helper class are located in a separate folder.
├── complete | ├── src/main | | ├── java/com/corbado/springboot | | | ├── FrontendController.java # Main controller which renders the HTML templates | | | └── JsonReader.java # Fetches json from web URLs | | | | | └── resources | | ├── application.properties # Contains the environment variables | | └── templates | | ├── index.html # Login page with the UI component | | └── profile.html # Profile page with user information | └── pom.xml # Contains info about the project and implementation details
Visit the Corbado developer panel to sign up and create your account (you'll see passkey sign-up in action here!).
After sign-up, a project wizard will guide you through the necessary steps to get everything up and running:
Application URL
and Relying Party ID
. The Application URL
is the URL where you embed the Corbado UI component. In this example, we set it to http://localhost:8080
. The Relying Party ID
is the domain (no protocol, no port and no path) where passkeys should be bound to. Here, it's localhost
(you can define both values als in the Settings > General > URLs of the Corbado developer panel).Afterwards, you'll see the relevant HTML / JavaScript code snippets you need to integrate into the project. The subsequent sections of this article will explain them in detail.
As another step, we create an API secret
which will be needed to request user data from the
Corbado backend. Please create an API secret
in Settings > Credentials > API secrets.
To initialize our project, we clone Java Spring Boot's starter repository with
git clone https://github.com/spring-guides/gs-spring-boot.git
In the /complete/src/main/java/com/example/springboot
folder, you will find
the HelloController.java
. We rename it to FrontendController.java
and use it
to serve plain HTML files when a user requests a certain path.
We will need the Corbado project ID and API secret in the next steps, so we'll add them as an
environment variable. For this, we create an application.properties
file under /complete/src/main/resources
and add them there:
.envprojectID=pro-xxx apiSecret=corbado1_xxx
Under /complete/src/main/resources/templates
create an index.html
file with
the content below. This will be our login page.
Add to the index.html
a script from Corbado as well as the code for the UI component.
index.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <link rel="stylesheet" href="https://unpkg.com/@corbado/web-js@2.8.0/dist/bundle/index.css" /> <script src="https://unpkg.com/@corbado/web-js@2.8.0/dist/bundle/index.js" defer></script> </head> <body> <script th:inline="javascript"> document.addEventListener('DOMContentLoaded', async () => { await Corbado.load({ projectId: /*[[${PROJECT_ID}]]*/, darkMode: "off", setShortSessionCookie: "true", }); const authElement = document.getElementById('corbado-auth'); Corbado.mountAuthUI(authElement, { onLoggedIn: () => { window.location.href = '/profile'; }, }); }); </script> <div id="corbado-auth"></div> </body> </html>
To make things work, we modify the index
endpoint in our FrontendController.java
to render our login page template. The Corbado project ID and API secret are taken from the
environment variables, used by SDK and are inserted into the template upon rendering.
FrontendController.java@Controller public class FrontendController { @Value("${projectID}") private String projectID; @Value("${apiSecret}") private String apiSecret; private final CorbadoSdk sdk; @Autowired public FrontendController( @Value("${projectID}") final String projectID, @Value("${apiSecret}") final String apiSecret) throws StandardException { final Config config = new Config(projectID, apiSecret); this.sdk = new CorbadoSdk(config); } @RequestMapping("/") public String index(final Model model) { model.addAttribute("PROJECT_ID", projectID); return "index"; } ....
After successful authentication, the Corbado UI component redirects the user. This page
displays information about the user and provides a button to log out. In the
templates folder, add a file profile.html
with the following
content:
profile.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <link rel="stylesheet" href="https://unpkg.com/@corbado/web-js@2.8.0/dist/bundle/index.css" /> <script src="https://unpkg.com/@corbado/web-js@2.8.0/dist/bundle/index.js" defer></script> </head> <body> <!-- Define passkey-list div and logout button --> <h2>:/protected</h2> <p>User ID: [[${USER_ID}]]</p> <p>Name: [[${USER_NAME}]]</p> <p>Email: [[${USER_EMAIL}]]</p> <div id="passkey-list"></div> <button id="logoutButton">Logout</button> <!-- Script to load Corbado and mount PasskeyList UI --> <script th:inline="javascript"> document.addEventListener('DOMContentLoaded', async () => { await Corbado.load({ projectId: /*[[${PROJECT_ID}]]*/, darkMode: "off", setShortSessionCookie: "true" // set short session cookie automatically }); // Get and mount PasskeyList UI const passkeyListElement = document.getElementById("passkey-list"); // Element where you want to render PasskeyList UI Corbado.mountPasskeyListUI(passkeyListElement); // Get logout button const logoutButton = document.getElementById('logoutButton'); // Add event listener to logout button logoutButton.addEventListener('click', function() { Corbado.logout() .then(() => { window.location.replace("/"); }) .catch(err => { console.error(err); }); }); })(); </script> </body> </html>
Next, create a profile()
method with annotation inside the
FrontendController.java
:
FrontendController.java@RequestMapping("/profile") public String profile() { return "profile"; }
We now need to obtain the user information we want to display in the
profile.html
template.
Before we can use information embedded in the session, we need to verify that
the session is valid. We therefore take the cbo_short_session
cookie (the
session) and verify its signature using the session service from the Corbado Java SDK.
This can be done with:
final SessionValidationResult validationResp = sdk.getSessions().getAndValidateCurrentUser(cboShortSession);
It takes the cbo_short_session
cookie, validates it and returns the UserID
and full name of the user.
The final code for the profile
mapping looks as follows:
FrontendController.java@RequestMapping("/profile") public String profile( final Model model, @CookieValue("cbo_short_session") final String cboShortSession) { try { // Validate user from token final SessionValidationResult validationResp = sdk.getSessions().getAndValidateCurrentUser(cboShortSession); // get list of emails from identifier service List<Identifier> emails; emails = sdk.getIdentifiers().listAllEmailsByUserId(validationResp.getUserID()); // model.addAttribute("PROJECT_ID", projectID); model.addAttribute("USER_ID", validationResp.getUserID()); model.addAttribute("USER_NAME", validationResp.getFullName()); // select email of your liking or list all emails model.addAttribute("USER_EMAIL", emails.get(0).getValue()); } catch (final Exception e) { System.out.println(e.getMessage()); model.addAttribute("ERROR", e.getMessage()); return "error"; } return "profile"; }
To start our application, we head into the /complete
directory of our app and
execute:
./mvnw spring-boot:run
When visiting http://localhost:8080, you should see the following screen:
After successful sign up / login, you see the profile page:
This tutorial showed how easy it is to add passwordless authentication with passkeys to a Java Spring Boot app using Corbado. Besides the passkey-first authentication, Corbado provides simple session management, that we used for a retrieval of basic user data. If you want to read more about how Corbado's session management please check the docs here. If you want to add Corbado to your existing app with existing users, please see our documentation here.
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.
Related Articles
Vercel Passkeys: Deploying Passkeys in Next.js App on Vercel
Nicolai - September 19, 2023
Flask Passkeys: How to Implement Passkeys with Python Flask
Janina - September 15, 2023
Tutorial: How to Add Passkeys to Node.js (Express) App
Lukas - October 16, 2023
Table of Contents