In this tutorial, we show you how to integrate passkeys into a Python FastAPI web app using Corbado’s passkey-first web-js library.
Nicolai
Created: December 19, 2023
Updated: April 30, 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 tutorial, we will be walking through the process of building a sample application with passkey authentication using the FastAPI web framework made for Python. To make passkeys work, we use Corbado's passkey-first web-js package that automatically connects to a passkeys backend.
If you want to run the project straight away, please follow the README of our sample app Github repository.
The result looks as follows:
Recent Articles
This tutorial assumes basic familiarity with FastAPI and Python as well as HTML and JavaScript. Let's dive in!
Our FastAPI project contains many files, but these are the most important ones:
├── .env # Contains all environment variables ├── main.py # Contains our webapplication (Handles routes) └── templates ├── index.html # Login page └── profile.html # Profile page
Visit the Corbado developer panel to sign up and create your account (you'll see the 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:8000
. The Relying Party ID
is the domain (no protocol,
no port, and no path) where passkeys should be bound. 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.
If you haven't installed FastAPI yet, do so by executing
pip install fastapi
To initialize our project, we simply create a main.py
file with the following content:
from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles import requests import os app = FastAPI() if __name__ == "__main__": import uvicorn uvicorn.run(app, host="127.0.0.1", port=8000)
We create a .env
file in the root folder with the following contents (using your values
of course):
PROJECT_ID=pro-xxx API_SECRET=corbado1_xxx
We use python-dotenv
to load the variables. Install it by running
pip install python-dotenv
Then, we can import it into our main.py
file and use it to obtain the variables from our
.env
file:
from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles import requests from dotenv import load_dotenv import os load_dotenv() app = FastAPI() PROJECT_ID = os.getenv("PROJECT_ID", "pro-xxx") API_SECRET = os.getenv("API_SECRET", "corbado1_xxx") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="127.0.0.1", port=8000)
For our templates, we create a templates folder. The login page will be in
templates/login.html
. It contains a script from Corbado with styles as well as the logic
needed to display the authentication component. See more info on how to use web-js
here
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://unpkg.com/@corbado/web-js@latest/dist/bundle/index.css" /> <script src="https://unpkg.com/@corbado/web-js@latest/dist/bundle/index.js"></script> </head> <body> <script> (async () => { await Corbado.load({ projectId: "{{ PROJECT_ID }}", darkMode: "off", setShortSessionCookie: "true", //set short session cookie automatically }); const authElement = document.getElementById("corbado-auth"); //Element where you want to render CorbadoAuth UI Corbado.mountAuthUI(authElement, { onLoggedIn: () => { //post login actions can be performed here. window.location.href = "/profile"; }, }); })(); </script> <div id="corbado-auth"></div> </body> </html>
Our second page is the profile page which the user will be redirected to after
authentication. Here, we show some basic user info which we will obtain in the Python
controller beforehand (we will create it later). We also provide a logout button that will
terminate the session Corbado had initiated after authentication. The page is located at
templates/profile.html
with the following content:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://unpkg.com/@corbado/web-js@latest/dist/bundle/index.css" /> <script src="https://unpkg.com/@corbado/web-js@latest/dist/bundle/index.js"></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> (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 the 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>
Since we only build a comparatively small app, our controller will be placed in our
main.py
file. Therefore, it will hold the entire web app logic comprised of two methods
one for the index page and one for the profile page. Inside the index method, we only need
to inject the project ID, but inside the profile method, we need to verify the integrity
of the Corbado session and extract the data stored in it. For this, we use the
Corbado Python SDK
(passkeys):
pip install passkeys
The main.py
file should look like this afterwards:
main.pyfrom typing import List from corbado_python_sdk.entities.session_validation_result import ( SessionValidationResult, ) from corbado_python_sdk.generated.models.identifier import Identifier from fastapi import FastAPI, Request, Response from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from dotenv import load_dotenv import os from corbado_python_sdk import ( Config, CorbadoSDK, IdentifierInterface, SessionInterface, ) load_dotenv() app = FastAPI() templates = Jinja2Templates(directory="templates") PROJECT_ID: str = os.getenv("PROJECT_ID", "pro-xxx") API_SECRET: str = os.getenv("API_SECRET", "corbado1_xxx") # Session config short_session_cookie_name = "cbo_short_session" # Config has a default values for 'short_session_cookie_name' and 'BACKEND_API' config: Config = Config( api_secret=API_SECRET, project_id=PROJECT_ID, ) # Initialize SDK sdk: CorbadoSDK = CorbadoSDK(config=config) sessions: SessionInterface = sdk.sessions identifiers: IdentifierInterface = sdk.identifiers @app.get("/", response_class=HTMLResponse) async def get_login(request: Request): return templates.TemplateResponse( "login.html", {"request": request, "PROJECT_ID": PROJECT_ID} ) @app.get("/profile", response_class=HTMLResponse) async def get_profile(request: Request): # Acquire cookies with your preferred method token: str = request.cookies.get(config.short_session_cookie_name) or "" validation_result: SessionValidationResult = ( sessions.get_and_validate_short_session_value(short_session=token) ) if validation_result.authenticated: emailList: List[Identifier] = identifiers.list_all_emails_by_user_id( user_id=validation_result.user_id or "" # at this point user_id should be non empty string since user was authenticated ) context = { "request": request, "PROJECT_ID": PROJECT_ID, "USER_ID": validation_result.user_id, "USER_NAME": validation_result.full_name, "USER_EMAIL": emailList[0].value, } return templates.TemplateResponse("profile.html", context) else: return Response( content="You are not authenticated or have not yet confirmed your email.", status_code=401, ) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="127.0.0.1", port=8000)
To run your FastAPI application, you need to install uvicorn:
pip install 'uvicorn[standard]'
Afterwards, use the following command:
uvicorn main:app --reload
Your FastAPI application will start when visiting http://localhost:8000 with a web browser. You should see the Authentication UI.
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 FastAPI app with 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
Flask Passkeys: How to Implement Passkeys with Python Flask
Janina - September 15, 2023
Django Passkeys: How to Implement Passkeys with Python Django
Nicolai - November 30, 2023
Table of Contents