Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django and 2 Factor Authentication

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/

Markus H

August 14, 2016
Tweet

More Decks by Markus H

Other Decks in Technology

Transcript

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

    View Slide

  2. Markus
    Holtermann
    Senior Software Engineer at LaterPay
    Django Core Developer
    @m_holtermann • github.com/MarkusH • markusholtermann.eu

    View Slide

  3. EASY MICROPAYMENTS FOR YOUR FAVORITE CONTENT
    USE NOW, PAY LATER.
    @laterpay • github.com/laterpay • laterpay.net
    W
    e
    are
    hiring

    View Slide

  4. Disclaimer

    View Slide

  5. History

    View Slide

  6. What is 2FA?

    View Slide

  7. Something you posses
    +
    Something you know

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  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

    View Slide

  13. Today

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  17. TOTP
    Time-based One-time Password
    https://tools.ietf.org/html/rfc6238

    View Slide

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

    View Slide

  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)

    View Slide

  20. YubiKey, Nitrokey
    Hardware One-time Password Generator

    View Slide

  21. Django Integration

    View Slide

  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

    View Slide

  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')

    View Slide

  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}
    )

    View Slide

  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')

    View Slide

  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

    View Slide

  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')

    View Slide

  28. Boilerplate

    View Slide

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

    View Slide

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

    View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  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')),
    ]

    View Slide

  37. Future?

    View Slide

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

    View Slide

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

    View Slide