Back to Blog

Add "Login with Passkeys" to your Django app

Vanessa Villa
Vanessa Villa

Passkeys, passkeys, passkeys! Everyone's talking about them. With Amazon rolling out passkeys last year and Google encouraging users to make them the default authentication method, it raises the question: How do I add them to my app?

How do passkeys work?

If you’re just interested in implementing them, you can skip this section. No offense taken 😉

FIDO2 multidevice credentials more often referred to as “passkeys” is a standard introduced by the FIDO alliance. It’s an extremely powerful way of using public-key cryptography to verify the identity of a user without passwords, multi-factor, etc. The public / private key pair is usually generated and securely stored on a hardware or software authenticator device.

To learn more about how passkeys and authenticators work in detail, check out the Secure By Design Hub article on passkeys.

How do I build it in?

In this tutorial, we’re going to leverage Pangea AuthN’s hosted pages to be able to quickly configure passkeys without building all the cryptographic mayhem from scratch 😅. To prove that it’s easy to add passkeys into any application in just a few minutes, I’m going to start with a fresh new Django app and implement passkeys in just a few steps.

Step 1: Create a new Django app and install the required dependencies

python -m pip install django
django-admin startproject mysite
cd mysite
python manage.py startapp authme
pip install python-dotenv pangea-django
python manage.py migrate

Let's go to mysite/mysite/settings.py to allow our app to use these packages.

We need to be able to:

  1. load a .env file

  2. access the pangea-django middleware

  3. load our HTML pages from a designated templates folder

from dotenv import load_dotenv
import os
from pathlib import Path

load_dotenv()

# Skip down to the middlware declaration. 
# Comment or remove the default django middleware. 
# Add Pangea's Auth Middleware

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    #"django.contrib.auth.middleware.AuthenticationMiddleware",
    "pangea_django.PangeaAuthMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

# Replace the default configuration with the templates below
# templates folder is where we will keep our html

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [os.path.join(BASE_DIR, "templates")],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

Step 2: Create an account on pangea.cloud

Head over to pangea.cloud and create an account for free. Then in the developer console, enable the “AuthN” service and grab the following tokens.

These tokens will be pasted in your .env config file as shown below. Create and add these tokens into mysite/.env file like this:

PANGEA_AUTHN_TOKEN=<PANGEA_AUTHN_TOKEN>
PANGEA_DOMAIN=<PANGEA_DOMAIN>
PANGEA_HOSTED_LOGIN=<PANGEA_HOSTED_LOGIN>

Step 3: Add the Pangea Auth components to the new Django app

Now we edit our mysite/authme/views.py file with the pangea_django package that maintains authentication context and state across the application.

So our authme/views.py file should look like this:

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from pangea_django import PangeaAuthentication, generate_state_param
import os

# Create your views here.

def landing(request):
    if request.user.is_authenticated:
        return redirect("/home")

    hosted_login = os.getenv("PANGEA_HOSTED_LOGIN")
    redirect_url = hosted_login + "?state=" + generate_state_param(request)

    return redirect(redirect_url)

def login_success(request):
    user = PangeaAuthentication().authenticate(request=request)
    if user:
        return redirect("/home")
    return redirect("/")

@login_required(login_url="/")
def logout(request):
    user = PangeaAuthentication().logout(request)
    return redirect("/logout")

@login_required(login_url="/")
def home(request):
    context = {}
    context["user"] = request.user
    return render(request, "home.html", context)

Next we need to map these views to paths. We do this in mysite/mysite/urls.py

from django.urls import path
import authme

urlpatterns = [
    path('', authme.views.landing),
    path('home', authme.views.home),
    path('login_success', authme.views.login_success),
    path('logout', authme.views.logout),
]

The app need to be able to access mysite/authme/views.py from mysite/mysite/urls.py. Add this line to mysite/authme/__init__.py

from . import views

Lastly we need a page for this authentication to render to. The way we make pages in Django is by creating a templates folder with html pages. Since authme.view.home is routing to <base url>/home, we need to call our page mysite/templates/home.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body,h1,h2,h3,h4,h5,h6 {font-family: "Lato", sans-serif;}
body, html {
  height: 100%;
  color: #b47b00;
  line-height: 1.8;
  background-color: antiquewhite;
}
table {
  margin: 0px auto;
  background-color: white;
  padding: 25px;
}
tr {
  font-family: Verdana, Tahoma, sans-serif;
  font-size: 20px;
  color: #rgb(158, 108, 0);
}
td {
  padding: 2px;
}
</style>
</head>
<body>
  <!-- Navbar (sit on top) -->
  <div>
    <a href="logout">LOGOUT</a>
  </div>
  <!-- Navbar (sit on top) -->
  <h1 style="text-align: center;">You are logged in!</h1>
    <table>
        <tr>
            <td>ID:</td>
            <td>{{ user.id }}</td>
        </tr>
        <tr>
            <td>Email:</td>
            <td>{{ user.email }}</td>
        </tr>
        <tr>
            <td>First Name:</td>
            <td>{{ user.first_name }}</td>
        </tr>
        <tr>
          <td>Last Name:</td>
          <td>{{ user.last_name }}</td>
        </tr>
        <tr>
          <td>Last Login:</td>
          <td>{{ user.last_login }}</td>
        </tr>
    </table>
</body>
</html>

Step 4: Add Redirect and Enable Passkeys in Pangea console

We need to add an authorized redirect, so that Pangea’s AuthN hosted pages can successfully redirect us back to the http://127.0.0.1:8000/home when a user is done authenticating.

So first, let’s go under General > Redirect (Callback) Settings and add http://127.0.0.1:8000/home as a redirect and save it.

Here comes the last step, let’s enable Passkeys! Head over to Single Sign On > Passkeys and enable it. Optionally you can choose to enable fallback authentication options based on your desired user experience.

Here’s a quick video on how you can enable it in settings:

Let's try it out! You can find the code in Github and watch the demo below.


If you are looking to add AuthN to other languages or frameworks, follow these tutorials:

Get updates in your inbox and subscribe to our newsletter

background landmass

We were recognized by Gartner®!

Pangea is a Sample Vendor for Composable Security APIs in the 2024 App Sec Hype Cycle report