Add "Login with Passkeys" to your Django app
Published: 2024-06-04 22:15:40

Passkeys, passkeys, passkeys! Everyone's talking about them. With [Amazon](https://www.theverge.com/2023/10/23/23928589/amazon-passkey-support-web-ios-shopping-mobile-app) rolling out passkeys last year and [Google](https://blog.google/technology/safety-security/passkeys-default-google-accounts/) 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](https://pangea.cloud/securebydesign/authn-using-passkeys/?utm_source=hashnode&utm_medium=passkeys-django-blog) 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 needed dependencies

```bash
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
    

```python
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](https://l.pangea.cloud/DN5AJWb)

Head over to [pangea.cloud](https://l.pangea.cloud/DN5AJWb) 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:

```bash
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:

```python
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`

```python
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`

```python
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`

```xml
<!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](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](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:

<iframe width="560" height="315" src="https://www.youtube.com/embed/M2kPx1WteEE?si=HxqHT_gvoNQNfoGp"></iframe>

Let's try it out! You can find the [code in Github](https://github.com/pangeacyber/pangea-django-authn-app) and watch the demo below.

<iframe width="560" height="315" src="https://www.youtube.com/embed/2pfrc1YI7nk?si=7TgceOr5Z7LJIbE9"></iframe>

---

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

* [React.js](https://pangea.cloud/blog/add-passkeys-to-reactjs-in-2mins/)
    
* [Next.js (page-router mode)](https://pangea.cloud/blog/add-login-with-passkeys-to-nextjs-in-2-mins/)