Quickstart¶

You can start using Django OTP WebAuthn in your Django projects by following these steps:

Install Django OTP WebAuthn from PyPI¶

First, install Django OTP WebAuthn from PyPI by running the following command:

pip install django-otp-webauthn

Add it to installed apps¶

Go to your <project>/settings.py file and add django_otp_webauthn and django_otp to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    "django_otp_webauthn",
    "django_otp",
    ...
]

Add OTPMiddleware¶

Add the django_otp.middleware.OTPMiddleware to the MIDDLEWARE setting in your <project>/settings.py file. Ensure you place it after the AuthenticationMiddleware:

MIDDLEWARE = [
    ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django_otp.middleware.OTPMiddleware",
    ...
]

Include URLs¶

Modify your <project>/urls.py file and add the required URL configuration:

from django.urls import include, path

urlpatterns = [
    ...
    path(
        "webauthn/",
        include("django_otp_webauthn.urls", namespace="otp_webauthn")
    ),
    ...
]

Configure settings for local and production environments¶

If you are configuring Django OTP WebAuthn for a production environment, update your <project>/settings.py file as follows:

# The name of the relying party (RP).
#This is sometimes shown to the user when they register a passkey.
OTP_WEBAUTHN_RP_NAME = "My Website Inc."

# This is necessary to bind the passkey to a specific domain.
# This should be the domain of your website.
OTP_WEBAUTHN_RP_ID = "your-domain.com"

# This is used to check the origin of the request and
# is used for security. It’s similar to Django's
# CSRF_TRUSTED_ORIGINS setting.
# The origins must always be a subdomain of
# the RP ID or the RP ID itself.
OTP_WEBAUTHN_ALLOWED_ORIGINS = [
    "https://your-domain.com",
    "https://subdomain.your-domain.com"
]

However, if you’re configuring Django OTP WebAuthn for local development, use the following settings in your <project>/settings.py file:

# The name of the relying party (RP). This is sometimes
# shown to the user when they register a passkey.
OTP_WEBAUTHN_RP_NAME = "My Website Inc."

# This is necessary to bind the passkey to a specific
# domain. This should be the domain of your website.
OTP_WEBAUTHN_RP_ID = "localhost"

# This is used to check the origin of the request and
# is used for security. It’s similar to
# Django's CSRF_TRUSTED_ORIGINS setting.
# The origins must always be a subdomain
# of the RP ID or the RP ID itself.
OTP_WEBAUTHN_ALLOWED_ORIGINS = ["http://localhost:8000"]

Update authentication backends¶

Modify your <project>/settings.py file to use django_otp_webauthn.backends.WebAuthnBackend in AUTHENTICATION_BACKENDS:

AUTHENTICATION_BACKENDS = [
    ...
    # Django’s default authentication backend
    "django.contrib.auth.backends.ModelBackend",
    "django_otp_webauthn.backends.WebAuthnBackend",
    ...
]

Add registration code¶

Now add your Django OTP WebAuthn registration snippet into your project. For example, add the following code in account_settings.html or a similar page where users manage their authentication methods:

<!-- account_settings.html -->
{% load otp_webauthn %}

{% comment %}
This template is displayed when WebAuthn registration
is supported. The template must contain a button
with the id `passkey-register-button`. To display status
and error messages, include an element with the id
`passkey-register-status-message`.
{% endcomment %}
<template id="passkey-registration-available-template">
    <div>
        <button type="button" id="passkey-register-button">
            Register Passkey
        </button>
        <div id="passkey-register-status-message"></div>
    </div>
</template>

{% comment %}
This template is displayed when WebAuthn registration
is not supported.
{% endcomment %}
<template id="passkey-registration-unavailable-template">
    <p>Sorry, your browser has no Passkey support</p>
</template>

{% comment %}
This placeholder element will be replaced with either the
contents of the `passkey-registration-available-template` or
the `passkey-registration-unavailable-template` template.
{% endcomment %}
<span id="passkey-registration-placeholder"></span>

{% comment %}
This template tag renders all the necessary <script> tags
for the default registration implementation
{% endcomment %}
{% render_otp_webauthn_register_scripts %}

Update login template for passwordless authentication¶

Now modify your login template to turn on passkey-based login:

{% load otp_webauthn %}

<form method="post">
    {% comment %} Suppose there is an username field on your page
    that has CSS selector: input[name="username"] {% endcomment %}
    <label for="id_username">Username</label>
    <input id="id_username" type="text" name="username" autocomplete="username">
    {% comment %} Other fields omitted for brevity {% endcomment %}

    {% comment %} This placeholder element will be replaced with either the
    contents of the `passkey-verification-available-template`
    or the `passkey-verification-unavailable-template` template. {% endcomment %}
    <span id="passkey-verification-placeholder"></span>

    {% comment %}
    This template is displayed when WebAuthn authentication
    is supported. Typically, you would want to display a button
    that the user can click to authenticate using a passkey.
    The template must contain a button with the id
    `passkey-verification-button`. To display status and
    error messages, include an element with the id
    `passkey-verification-status-message`.
    {% endcomment %}
    <template id="passkey-verification-available-template">
        <button type="button" id="passkey-verification-button">
            Login using a Passkey
        </button>
        <div id="passkey-verification-status-message"></div>
    </template>

    {% comment %}
    This template is displayed when WebAuthn is not supported.
    {% endcomment %}
    <template id="passkey-verification-unavailable-template">
        <p>Sorry, your browser has no Passkey support</p>
    </template>

    {% comment %}
    This template tag renders all the necessary <script> tags
    for the default verification implementation.

    To make browsers automatically suggest a passkey when you
    focus the username field, make sure `username_field_selector`
    is a valid CSS selector.

    The username_field_selector parameter is only required to
    make 'passwordless authentication' work.
    {% endcomment %}
    {% render_otp_webauthn_auth_scripts username_field_selector="input[name='username']" %}
</form>

Migrate your database¶

Finally, run the following command to apply database migrations:

python manage.py migrate

If you configured your project for local environment, run your server:

python manage.py runserver

Note

When running the development server, ensure you access your site using http://localhost:8000, as WebAuthn doesn’t work on IP addresses such as http://127.0.0.1:8000.

Once you’ve done this, you will see the following on your login page:

  • a Register Passkey button on the login page

  • a Login using a Passkey button on the login page