I want to integrate Yubikey with my Django application. Whenever I try to add a 2FA Yubikey: localhost:8080/account/two_factor/setup/
I get a 403 HTTP Forbidden
error. How do I implement the Yubikey in Django, because when I use these tutorials then the implementation fails.
When I follow the example here to do the same I get a Server Error 500
I followed these tutoriols:
This is what I have done:
pip install django-two-factor-auth
pip install django-otp-yubikey
pip install django-otp
I use Python 3.7.3 and Django 3.0.8
In my settings.py
I added:
INSTALLED_APPS = [
...
# OTP
'django_otp',
'django_otp.plugins.otp_static',
'django_otp.plugins.otp_totp',
'two_factor',
'otp_yubikey',
...
]
MIDDLEWARE = [
...
'django_otp.middleware.OTPMiddleware',
...
]
LOGIN_URL = 'two_factor:login'
TWO_FACTOR_PATCH_ADMIN = True
In my urls.py
I added:
from two_factor.urls import urlpatterns as tf_urls
from django_otp.views import LoginView
...
urlpatterns = [
...
# OTP
path('', include(tf_urls)),
...
]
And in my views.py
I have added:
from django_otp.decorators import otp_required
@otp_required
def home_view(request):
return render(request, 'home.html', {'result': 'test')
My home.html
is simple:
<html>
<head></head>
<body><p>test</p></body>
</html>
Stacktrace:
ERROR 2020-07-20 14:14:24,571 log 35773 123145342668800 Internal Server Error: /account/two_factor/setup/
Traceback (most recent call last):
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/views/generic/base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/utils/decorators.py", line 43, in _wrapper
return bound_method(*args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/utils/decorators.py", line 43, in _wrapper
return bound_method(*args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/formtools/wizard/views.py", line 244, in dispatch
response = super().dispatch(request, *args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/views/generic/base.py", line 97, in dispatch
return handler(request, *args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/two_factor/views/utils.py", line 156, in post
return super().post(*args, **kwargs)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/formtools/wizard/views.py", line 294, in post
if form.is_valid():
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/forms/forms.py", line 180, in is_valid
return self.is_bound and not self.errors
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/forms/forms.py", line 175, in errors
self.full_clean()
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/forms/forms.py", line 376, in full_clean
self._clean_fields()
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/django/forms/forms.py", line 397, in _clean_fields
value = getattr(self, 'clean_%s' % name)()
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/two_factor/forms.py", line 84, in clean_token
return super().clean_token()
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/two_factor/forms.py", line 70, in clean_token
if not self.device.verify_token(token):
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/otp_yubikey/models.py", line 280, in verify_token
response = client.verify(token)
File "/Users/user_anon/work_folder/home_dir/venv/lib/python3.7/site-packages/yubiotp/client.py", line 48, in verify
stream = urlopen(url)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 222, in urlopen
return opener.open(url, data, timeout)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 531, in open
response = meth(req, response)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 641, in http_response
'http', request, response, code, msg, hdrs)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 569, in error
return self._call_chain(*args)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 503, in _call_chain
result = func(*args)
File "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 649, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden
Sigh, Ok so I edited the client.py
in yubiotp
so have full control over the URL that is used in urlopen
replaced
stream = urlopen(url)
with
from app import views as v_yow
...
stream = v_yow.test(url)
The views.py
:
from six.moves import xrange
from six.moves.urllib.parse import urlencode
from six.moves.urllib.request import urlopen
import urllib
def test(url):
"""
"""
print('Testing 123')
print(url)
url_without_http = url[4:]
url_with_https = 'https' + url_without_http
stream = urlopen(url_with_https)
return stream
And this works excellent. So the problem here is that the requested URL was a HTTP url and not a HTTPS url. The HTTP url redirects to HTTPS but fails to word in the lib urlopen(url)
.
So to make this work without having this weird "fix" make sure you set the use_ssl
to True
in :
classotp_yubikey.models.ValidationService(*args, **kwargs)[source]