Django and 2 Factor Authentication

Cd7648c536b4dbe940246b74044fbc52?s=47 Markus H
August 14, 2016

Django and 2 Factor Authentication

My talk from DjangoCon AU 2016

Speaker notes / blog post: https://markusholtermann.eu/2016/09/2-factor-authentication-in-django/

Cd7648c536b4dbe940246b74044fbc52?s=128

Markus H

August 14, 2016
Tweet

Transcript

  1. Django and 2 Factor Authentication Markus Holtermann • @m_holtermann •

    DjangoCon Australia 2016
  2. Markus Holtermann Senior Software Engineer at LaterPay Django Core Developer

    @m_holtermann • github.com/MarkusH • markusholtermann.eu
  3. EASY MICROPAYMENTS FOR YOUR FAVORITE CONTENT USE NOW, PAY LATER.

    @laterpay • github.com/laterpay • laterpay.net W e are hiring
  4. Disclaimer

  5. History

  6. What is 2FA?

  7. Something you posses + Something you know

  8. None
  9. None
  10. None
  11. None
  12. Due to the risk that SMS messages may be intercepted

    [...], implementers [...] SHOULD [...] consider alternative authenticators. If the out of band verification is to be made using a SMS message on a public mobile telephone network, the verifier SHALL verify that the pre-registered telephone number being used is actually associated with a mobile network and not with a VoIP [...]. NIST — Digital Authentication Guideline — Ch 5.1.3.2. — https://pages.nist.gov/800-63-3/sp800-63b.html
  13. Today

  14. HOTP HMAC-based One-time Password https://tools.ietf.org/html/rfc4226

  15. HOTP(key, counter) = Trunc(HMAC(‘sha1’, counter, key))

  16. import hashlib, hmac, struct def hotp(key, counter, digits=6): msg =

    struct.pack(b'>Q', counter) hs = hmac.new(key, msg, hashlib.sha1).digest() hs = list(iter(hs)) # Truncate offset = hs[19] & 0x0f bin_code = ( (hs[offset] & 0x7f) << 24 | hs[offset + 1] << 16 | hs[offset + 2] << 8 | hs[offset + 3] ) return bin_code % pow(10, digits) https://bitbucket.org/psagers/django-otp/src/acbccb8f621b00dda 1d5018be54ab55e162586b3/django-otp/django_otp/oath.py
  17. TOTP Time-based One-time Password https://tools.ietf.org/html/rfc6238

  18. TOTP(key, t, t0=0, steps=30s) = HOTP(key, (t - t0) /

    steps)
  19. import time def totp(key, t=None, t0=0, steps=30, digits=6): t =

    int(t or time.time()) counter = (t - int(t0)) // int(steps) return hotp(key, counter, digits)
  20. YubiKey, Nitrokey Hardware One-time Password Generator

  21. Django Integration

  22. import binascii, time from django.conf import settings from django.db import

    models from .utils import new_key, totp class TOTPDevice(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL) key = models.CharField(max_length=20, default=new_key) step = models.PositiveSmallIntegerField(default=30) t0 = models.BigIntegerField(default=0) last_t = models.BigIntegerField(default=-1) def verify_token(self, other): key = binascii.unhexlify(self.key.encode()) t = time.time() token = totp(key, t, self.t0, self.step) if t > self.last_t and token == other: self.last_t = t self.save(update_fields=['last_t']) return True return False # Adapted from django_otp
  23. from django import forms from django.core.exceptions import ValidationError class OTPTokenForm(forms.Form):

    token = forms.IntegerField(min_value=0, max_value=999999) def __init__(self, user, *args, **kwargs): self.user = user def clean(self): if not self.user.totpdevice.verify_token( self.cleaned_data['token']): raise ValidationError('Invalid token')
  24. from django.conf import settings from django.contrib.auth.decorators import login_required from django.shortcuts

    import redirect, render from .forms import OTPTokenForm @login_required def otp_token_view(request): if request.session.get('verified', False): return redirect(settings.LOGIN_REDIRECT_URL) if request.method == 'POST': form = OTPTokenForm(request.user, request.POST) if form.is_valid(): request.session['verified'] = True return redirect(settings.LOGIN_REDIRECT_URL) else: form = OTPTokenForm(request.user) return render( request, 'otp_token_form.html', {'form': form} )
  25. import functools from django.conf import settings from django.shortcuts import redirect

    def is_verified(view): @functools.wraps(view) def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return view(request, *args, **kwargs) return redirect('otp-token-view') return wrapper class IsVerified(object): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return super().dispatch(request, *args, **kwargs) return redirect('otp-token-view')
  26. import functools from django.conf import settings from django.shortcuts import redirect

    def is_verified(view): @functools.wraps(view) def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return view(request, *args, **kwargs) return redirect('otp-token-view') return wrapper class IsVerified(object): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return redirect(settings.LOGIN_URL) if request.session.get('verified', False): return super().dispatch(request, *args, **kwargs) return redirect('otp-token-view') ≥ Django 1.10
  27. from django.http import HttpResponse from django.views.generic import View from .decorators

    import is_verified from .mixins import IsVerified @is_verified def my_view(request): return HttpResponse('This is a 2FA function view') class MyView(IsVerified, View): def get(self, request, *args, **kwargs): return HttpResponse('This is a 2FA class view')
  28. Boilerplate

  29. django-otp https://bitbucket.org/psagers/django-otp

  30. django-two-factor-auth https://github.com/Bouke/django-two-factor-auth

  31. None
  32. None
  33. None
  34. None
  35. None
  36. INSTALLED_APPS = [ 'django_otp', 'django_otp.plugins.otp_static', 'django_otp.plugins.otp_totp', 'two_factor.apps.TwoFactorConfig', 'django.contrib.admin', ... ]

    MIDDLEWARE_CLASSES = [ ... 'django_otp.middleware.OTPMiddleware', ] TWO_FACTOR_FORCE_OTP_ADMIN = True urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include('two_factor.urls', namespace='two_factor')), ]
  37. Future?

  38. On Cybersecurity and Being Targeted http://www.kennethreitz.org/essays/on-cybersecu rity-and-being-targeted

  39. Thanks @m_holtermann • github.com/MarkusH • markusholtermann.eu • laterpay.net Questions?