Quickstart

Installation

Install from PyPI with pip:

pip install maykin-2fa

This will install the WebAuthn (hardware token support) dependencies too.

Settings

As this library bundles django-two-factor-auth, the installation instructions of it are relevant. For typical Maykin usage, you’ll want to:

Add the required INSTALLED_APPS entries

INSTALLED_APPS = [
    ...,
    # Required by django-two-factor-auth and its dependencies
    "django_otp",
    "django_otp.plugins.otp_static",
    "django_otp.plugins.otp_totp",
    "two_factor",
    # if you do *not* want to use hardware tokens, you can omit the next line
    "two_factor.plugins.webauthn",
    "maykin_2fa",
    ...,
]

Set up our middleware override

We ship a modified django-otp middleware - so instead of django_otp.middleware.OTPMiddleware, you must set maykin_2fa.middleware.OTPMiddleware, after the authentication middleware:

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

urls.py

The admin must be monkeypatched so that third party packages registering to the default admin are also properly 2FA-protected. Unfortunately monkeypatching seems to be the only viable approach.

This should not be a problem in tests etc. since we can mark authentication backends as no-2FA-required, such as the django webtest backend.

In your root urls.py, ensure the admin is patched and include the URLs to the custom views:

from maykin_2fa import monkeypatch_admin
from maykin_2fa.urls import urlpatterns, webauthn_urlpatterns

monkeypatch_admin()

urlpatterns = [
    # should come BEFORE the default admin URLs to override behaviour.
    path("admin/", include((urlpatterns, "maykin_2fa"))),
    # if you do *not* want to use hardware tokens, you can omit the next line
    path("admin/", include((webauthn_urlpatterns, "two_factor"))),
    path("admin/", admin.site.urls),
]

Additionally, the default login patching can/should be disabled as it’s no longer relevant by the above monkeypatching.

TWO_FACTOR_PATCH_ADMIN = False

Congigure WebAuthn relying party

This is required when using the WebAuthn plugin so you can use hardware tokens.

TWO_FACTOR_WEBAUTHN_RP_NAME = "ACME"

The relying party name is used to scope a device too - make sure the name is application and intstance specific enough.

See the django-two-factor-auth documentation for more WebAuthn configuration options.

Additional settings you probably want to use:

# only allow hardware tokens (and Android devices on Chromium-based browsers)
TWO_FACTOR_WEBAUTHN_AUTHENTICATOR_ATTACHMENT = "cross-platform"

Configure allow list to skip 2FA-enforcement

By default, this package ensures the admin enforces 2FA. However, when logging in through OpenID Connect or other Single-Sign-On solutions, this can lead to double 2FA flows. Since these alternative login flows typically come with a custom Django authentication backend, you can add them to an allowlist to bypass the application MFA.

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "mozilla_django_oidc_db.backends.OIDCAuthenticationBackend",
]

MAYKIN_2FA_ALLOW_MFA_BYPASS_BACKENDS = [
    "mozilla_django_oidc_db.backends.OIDCAuthenticationBackend",
]

Tip

Users who log in with username + password in the admin and have any MFA-device configured on their account will still get the MFA prompt, even if the authentication backend is present in the bypass list.

This may seem unintuitive, however, it would be unexpected for the users who went through the effort of securing their account that this is now suddenly no longer active.

Usage

Should be plug and play - there is no additional frontend stuff.

You can run python manage.py check to diagnose potential problems.

We recommend putting a link in the admin user links to the maykin_2fa:account_security view where users can manage their backup tokens.

To include the available endpoints, update your root urls.py like below:

urlpatterns = [
    ...,
    path("api/", include("maykin_2fa.api.urls")),
    ...,
]

See The API reference section for more information about the available endpoints.