An extension to Django's ModelBackend that supports passkeys.
Passkeys are an extension to Web Authentication API that allow the user to log in to a service using a securely stored key on the same or another device.
This app is a slimmed-down version of django-mfa2.
Passkeys are supported on all major browsers and operating systems. See passkeys.io for details. On May 3, 2023, Google enabled Passkeys for the users to login, killing the password for enrolled users.
pip install django-passkeys
Currently, django-passkeys supports Django 2.0+ and Python 3.7+.
Important note: Passkeys only work in a secure context, i.e., when using SSL/HTTPS.
- In your
settings.py
, add the application to your installed apps.Note: Only apps listed beforeINSTALLED_APPS=( '......', 'passkeys', '......', )
passkeys
are able to override templates and static files of this app. - Run
collectstatic
:python manage.py collectstatic
- Run
migrate
:python manage.py migrate
- Make the following changes to your settings.py file:
Note: Starting with v1.1,
# Update authentication backends, replacing 'django.contrib.auth.ModelBackend' with 'passkeys.backend.PasskeyModelBackend' AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] # Server RP ID for FIDO2 - Use the full domain name (but without subdomains like "www.") of your project FIDO_SERVER_ID="localhost" # Server Name - You can choose any name here, preferably the one of your application. FIDO_SERVER_NAME="TestApp" # Optionally, restrict the use to cross platform (roaming) or platform authenticators. # See https://www.w3.org/TR/webauthn/#sctn-authenticator-attachment-modality for details import passkeys KEY_ATTACHMENT = None # No restriction (default if left out) KEY_ATTACHMENT = passkeys.Attachment.CROSS_PLATFORM # Only cross-platform / roaming authenticators allowed KEY_ATTACHMENT = passkeys.Attachment.PLATFORM # Only platform authenticators allowed
FIDO_SERVER_ID
and/orFIDO_SERVER_NAME
can be a callable to support multi-tenants web applications, therequest
is passed to the called function. - Add passkeys to urls.py:
urlpatterns= [ '...', path('passkeys/', include('passkeys.urls')), '....', ]
- In your login view, change the authenticate call to include the request as follows:
Note: If you use
user = authenticate(request, username=request.POST["username"], password=request.POST["password"])
django.contrib.auth
's LoginView, you must override theform_class
used. - Integrate passkey login in your login template (e.g.
login.html
):- Give an id to your login form, e.g 'loginForm', which will be used below.
- Inside the form, add
<input type="hidden" name="passkeys" id="passkeys" /> <button class="btn btn-block btn-dark" type="button" onclick="authn('loginForm')"> <img src="{% static 'passkeys/imgs/FIDO-Passkey_Icon-White.png' %}" style="width: 24px" alt="Passkey icon" /> </button> {% include 'passkeys.js' %}
- To match the look and feel of your project, Passkeys includes
base.html
, but it needs blocks namedhead
&content
to add its content to them. Notes:Passkeys_base.html
extendsbase.html
.- You can override
PassKeys_base.html
, which is used byPasskeys.html
so you can control the styling better. - Currently,
PassKeys_base.html
depends on JQuery and Bootstrap.
- Somewhere in your app, add a link to 'passkeys:home'
<a href="{% url 'passkeys:home' %}">Manage Passkeys</a>
For more information about how to set it up, please see the 'example' app and the EXAMPLE.md document.
Once the backend is used, there will be a passkey
key in request.session
.
If the user used a passkey, then request.session['passkey']['passkey']
will be True
and the key information will be
available in the following format:
# request.session['passkey'] if the user signed in with a passkey
{'passkey': True, 'name': 'Chrome', 'id': 2, 'platform': 'Chrome on Apple', 'cross_platform': False}
cross_platform
: means that the user used a key from another platform, so there is no key locally to the device used to login. This might be the case e.g if a user used an Android phone to log in on Windows.
If the user didn't use a passkey, then request.session['passkey']['passkey']
will be set to False
:
# request.session['passkey'] if the used didn't sign in with a passkey
{'passkey': False}
If you want to check if the user can be enrolled to use a platform authenticator, you can do the following in your main page:
<div id="pk" class="alert alert-info" style="display: none">
Your device supports passkeys! <a href="{% url 'passkeys:enroll' %}">Enroll now</a>
</div>
<script type="text/javascript">
{% include 'check_passkeys.js' %}
function showPasskeyEnrollmentInfo() {
$('#pk').show();
}
$(document).ready(check_passkey(true, showPasskeyEnrollmentInfo))
</script>
The check_passkey
function parameters are as follows:
platform_authenticator
: if the service requires only a platform authenticator (e.g TouchID, Windows Hello or Android SafetyNet)success_func
: function to call if a platform authenticator is found and if the user didn't login with a passkeyfail_func
: function to call if no platform authenticator is found (optional).
Conditional UI is a way for the browser to automatically prompt the user to use the passkey to log in, if he has one. The following screenshot shows how this might look in macOS:
Starting with v1.2, you can use Conditional UI by adding the following to your login page:
- Add
webauthn
to the autocomplete attribute of the username field as shown below.<input name="username" placeholder="username" autocomplete="username webauthn">
- Add the following to the page JavaScript, where
loginForm
is the id of your login form.window.addEventListener("load", () => checkConditionalUI('loginForm'));
To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.