I am trying to add two factor authentication to the Django Admin Site as an extra layer of security using django-otp (version 1.2.2) and qrcode (version 7.4.2). Both have been installed as part of django-two-factor-auth (version 1.15.4). Furthermore, I have set up a custom user model in a custom Postgresql database. For the setup of the custom user model and database, I followed the tutorial Django Real Estate App Using Multiple Users & Databases by Brian Brkic (especially parts 1, 2, 3, and 10). For the implementation of the two factor authentication, I have started with the tutorial Towards Django Two Factor Authentication Integration by Very Academy, but continued with Django Two Factor Authentication Example because I thought I could avoid all the extra forms.
The migration to the custom database python manage.py migrate --database=users worked well and the Admin Site at localhost:8000/admin is presented properly, including the field for the OTP code. However, when I fill in the username, password and OTP code in the login form and hit send, I get the error
ImproperlyConfigured at /admin/login/
settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.
I came across this type of error in a different project and in that case it was caused by a missing app label in my router file. So, it can very well be that I am missing something here as well. Here is my router.py:
class AuthRouter:
route_app_labels = {'user', 'admin', 'contenttypes', 'sessions', 'auth', 'otp_totp',}
def db_for_read(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'users'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'users'
return None
def allow_relation(self, obj1, obj2, **hints):
if (
obj1._meta.app_label in self.route_app_labels or
obj2._meta.app_label in self.route_app_labels
):
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label in self.route_app_labels:
return db == 'users'
return None
An here is the snippet describing the database setup and the router setup in settings.py:
...
DATABASES = {
'default': {},
'users': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'postgres-db',
'USER': 'postgres-user',
'PASSWORD': 'postgres-user-password'
},
}
DATABASE_ROUTERS = ['user.router.AuthRouter',]
...
To follow my hunch that the error is caused by a migration issue, I made a clone of this project and replaced the postgresql database with the default sqlite database. Using the default database, I was able to implement 2FA successfully. Below you find the setup (in snippets) for the successful test. settings.py:
INSTALLED_APPS = [
...
'user',
'django_otp',
'django_otp.plugins.otp_totp',
]
MIDDLEWARE = [
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
...
]
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
and the project's urls.py:
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
User = get_user_model()
from user.admin import UserAdmin
from django.urls import path, include
...
from django_otp.admin import OTPAdminSite
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_otp.plugins.otp_totp.admin import TOTPDeviceAdmin
class OTPAdmin(OTPAdminSite):
pass
admin_site = OTPAdmin(name='OTPAdmin')
admin_site.register(User,UserAdmin)
admin_site.register(Group)
admin_site.register(TOTPDevice, TOTPDeviceAdmin)
urlpatterns = [
...
path('auth/user/', include('user.urls')),
#path('admin/', admin.site.urls),
path('admin/', admin_site.urls),
]
So, the only difference between the prostgresql-setup and the default-setup is indeed the database setup in settings.py.
I hope that someone has the right hint!