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: February 17, 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.
2. FastAPI Passkey Project Prerequisites
3. Repository Structure for FastAPI Passkey Project
4. Set Up Your Corbado Account and Project
5. FastAPI Passkey Project Setup
5.2 Configure Environment Variables
Recent Articles
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:
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.
Table of Contents
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