django-duo-universal-auth
A lightweight middleware application that adds a layer on top of any number of existing authentication backends, enabling 2FA with the user's Duo account using the Universal Prompt after signing in with your Django application.
Note: In order to interface this middleware with Duo, you must create a new Duo Web SDK application from within your organization's Duo Admin Portal and enable the "Show new Universal Prompt" setting. You will acquire a Client ID, Client Secret, and API Hostname, of which you will include in your settings.py
file in the format listed below. It is strongly recommended not to hardcode these values in the settings file itself.
From Duo's documentation for protecting applications:
Treat your Secret key or Client ID like a password The security of your Duo application is tied to the security of your Secret key (skey) or Client secret (client_secret). Secure it as you would any sensitive credential. Don't share it with unauthorized individuals or email it to anyone under any circumstances!
Installation
To install the middleware application, use the following pip
command (or equivalent for your package manager application):
pip install django-duo-universal-auth
settings.py
file)
Sample Configuration (in your First, add the package to your INSTALLED_APPS
list variable:
INSTALLED_APPS = [
# ...
'duo_universal_auth', # Add this!
]
Next, add the path for the middleware application to the MIDDLEWARE
list variable:
MIDDLEWARE = [
# ...
'duo_universal_auth.middleware.DuoUniversalAuthMiddleware', # Add this!
]
Then, add a new DUO_UNIVERSAL_AUTH
configuration variable:
DUO_UNIVERSAL_AUTH = {
'MAIN': {
'DUO_HOST': '
'
,
'CLIENT_ID': '
'
,
'CLIENT_SECRET': '
'
,
'AUTH_BACKENDS': [
'django.contrib.auth.backends.ModelBackend',
],
'FAIL_ACTION': 'CLOSED'
}
}
Duo API Callback Setup
Note: This step allows the application to communicate with Duo. If the view is not registered, the application will raise a NoReverseMatch
error upon starting the Duo authentication flow.
To create the callback for the API to communicate with, you must add an entry to your urlpatterns
variable from within your application's urls.py
file (with any prepending path you choose):
from django.urls import path, include
urlpatterns = [
# ...
path('duo/', include('duo_universal_auth.urls')), # Add this!
]
Configuration Docs
Configurations for each Duo application are specified within individual dictionary objects inside a parent DUO_UNIVERSAL_AUTH
dictionary each containing the following values:
DUO_HOST
Required: True
Represents the API Hostname for your organization's Duo API.
'DUO_HOST': 'api-XXXXXXX.duosecurity.com'
CLIENT_ID
Required: True
Represents the Client ID for your application registered from within the Duo Admin Portal.
'CLIENT_ID': 'DIXXXXXXXXXXXXXXXXXX'
CLIENT_SECRET
Required: True
Represents the Client Secret for your application registered from within the Duo Admin Portal.
'CLIENT_SECRET': 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
AUTH_BACKENDS
Required: True
A list of authentication backends that the middleware will work with for the specific application. The Duo authentication middleware will only execute upon a successful authentication result from one of these backends.
'AUTH_BACKENDS': [
'django.contrib.auth.backends.ModelBackend',
]
FAIL_ACTION
'CLOSED'
)
Required: False (Default: How the middleware should respond should the Duo authentication server be unavailable (from failing the preliminary health check).
'CLOSED'
: Log out the user and return to the login page, disallowing any authentication while Duo servers are unavailable.'OPEN'
: Temporarily bypass Duo authentication until the Duo servers become available upon a future authentication attempt.
'FAIL_ACTION': 'CLOSED'
USERNAME_REMAP_FUNCTION
Required: False
An optional one-argument function that takes in the current Django HttpRequest
object and returns the current authenticated user's username to send for Duo authentication. If unspecified, the username from HttpRequest.user
will be used.
'USERNAME_REMAP_FUNCTION': lambda r: r.user.username # Mimics default behavior
Post-Authentication Redirect
Once successfully authenticated with Duo, the middleware will automatically redirect the user to the path specified in the DUO_NEXT_URL
session variable, falling back to the LOGIN_REDIRECT_URL
settings variable if it is not present. Because the next
query parameter does not travel along with the Duo authentication flow, this session variable is not assigned in the middleware, but can be assigned using a custom instruction in your AuthenticationBackend. This is a feature that I plan to add to this package either through a decorator function or other means. If anyone has any ideas on how to implement this, feel free to submit a pull request!