Should contrib.auth include support for 2fa out of the box?

374 views
Skip to first unread message

Tim Graham

unread,
Oct 26, 2015, 1:22:46 PM10/26/15
to Django developers (Contributions to Django itself)

On Trac [1], Alex says, "Django did a tremendous service to its users by making strong password hashing be the default. The world is pushing forward, and now 2fa is the next standard that many sites fail to meet. Django should include support for 2fa out of the box, ideally with support for both u2f and TOTP (Google Authenticator)."


Doing a quick search, I found https://github.com/Bouke/django-two-factor-auth as a possible existing implementation that might be a starting point if we decide to integrate something. What do you think? One sticking point could be that it uses a ThreadLocals middleware. I didn't look to see how "necessary" that is.


[1] https://code.djangoproject.com/ticket/25612

Donald Stufft

unread,
Oct 26, 2015, 1:30:25 PM10/26/15
to Tim Graham, django-d...@googlegroups.com
I agree with Alex, no idea about that particular implementation though. It supports a lot of different implementations of two factor, though I suspect Django wouldn’t need all of those things. I think it would be reasonable to define something like auth_backends, but for 2fa and just ship u2f and TOTP by default.
> --
> You received this message because you are subscribed to the Google Groups "Django developers
> (Contributions to Django itself)" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
> To post to this group, send email to django-d...@googlegroups.com.
> Visit this group at http://groups.google.com/group/django-developers.
> To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/5ae7be8e-949c-4074-b613-04ca2a62fed8%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-----------------
Donald Stufft
PGP: 0x6E3CBCE93372DCFA // 7C6B 7C5D 5E2B 6356 A926 F04F 6E3C BCE9 3372 DCFA


Aymeric Augustin

unread,
Oct 26, 2015, 2:18:20 PM10/26/15
to django-d...@googlegroups.com
On 26 oct. 2015, at 18:22, Tim Graham <timog...@gmail.com> wrote:
> What do you think?

I would very much like Django to have 2FA out of the box.

Let’s bring django.contrib.auth kicking and screaming into the 2010’s ;-)

I even considered crowdfunding it but that didn’t go much beyond idle thoughts.

--
Aymeric.

Josh Smeaton

unread,
Oct 26, 2015, 6:47:41 PM10/26/15
to Django developers (Contributions to Django itself), timog...@gmail.com
Having pluggable 2fa backends is a great idea.

Many sites that allow 2fa have it as an option per user. I would think Django would allow the same. Allow admins to force 2fa, or allow Users to choose if they'd like it enabled.

There'd have to be ORM/Model support (presumably) for user choices. A migration may be necessary, or just a completely separate table. Just things to consider.

Cheers

Joey Wilhelm

unread,
Oct 26, 2015, 7:46:47 PM10/26/15
to django-developers@googlegroups com

Fwiw, 2fa is on my short list of things to implement into my current project. It's a fairly important feature to me, as this is a financial project. And that particular implementation is precisely what I was looking to use. I would happily contribute money and/or time toward this implementation, especially if there was a happy upgrade path from Bouke's library.

Russell Keith-Magee

unread,
Oct 26, 2015, 9:31:49 PM10/26/15
to Django Developers

+1. Sounds like a great idea to me.

Russ %-)

--

Dheerendra Rathor

unread,
Oct 26, 2015, 10:48:22 PM10/26/15
to Django Developers
Other then u2f and TOTP, I'll favour for email and sms (using external api like twilio) based OTP as well. Keeping different pluggable backends will be better in my opinion. 

Florian Apolloner

unread,
Oct 27, 2015, 10:53:08 AM10/27/15
to Django developers (Contributions to Django itself)
Yes, I would like to see that in contrib.auth, which will require rewriting the backends and introduce a auth pipeline ala django-social-auth (has/had) -- I was planning to get some thoughts about that on DUTH. Also see this (short) twitter discussion: https://twitter.com/jacobian/status/651527202119397376 -- so the need is definitely there.

Cheers,
Florian

Florian Apolloner

unread,
Oct 27, 2015, 10:57:06 AM10/27/15
to Django developers (Contributions to Django itself)

On Tuesday, October 27, 2015 at 12:46:47 AM UTC+1, Joey Wilhelm wrote:

Fwiw, 2fa is on my short list of things to implement into my current project. It's a fairly important feature to me, as this is a financial project. And that particular implementation is precisely what I was looking to use. I would happily contribute money and/or time toward this implementation, especially if there was a happy upgrade path from Bouke's library.


I think if Django supports 2fa the changes will go much deeper than what django-otp etc currently do, in that sense it is unlikely that there will be any supported upgrade path. If you can transfer the current data to the new system is hard to say, but probably doable…

bliy...@rentlytics.com

unread,
Oct 27, 2015, 4:01:40 PM10/27/15
to Django developers (Contributions to Django itself)
+1

This sounds like a great feature, depending on the implementation.

m.lev...@web.de

unread,
Oct 3, 2016, 1:08:02 PM10/3/16
to Django developers (Contributions to Django itself)
I would like to work on this ticket. As for the implementation there doesn't seem to be much choice. The implementation with the most features is from Bouke. It supports U2F, TOTP, SMS and phone call (with Twilio by default). Beside that one only a few packages exist:

Based on the ticket description and the django developers discussion U2F and TOTP are the most desired authentication methods. So I would like to integrate them (orienting on Bouke's implementation) first. And if SMS and email based authentication are also desired I would go about them next.

Aymeric Augustin

unread,
Oct 3, 2016, 1:35:09 PM10/3/16
to django-d...@googlegroups.com
Hello,

FYI django-two-factor-auth builds upon django-otp; they aren't alternatives. 

This is an ambitious project. I suggest to start by getting a better understanding of what these libraries do, what the different scenarios for two factor authentication are, and writing a DEP to describe the APIs that would be added to Django. Look at the current public APIs of django.contrib.auth and figure out what would be needed to support 2FA with similar principles.

Starting from the lower layers — the equivalent of django-otp — seems (to me) to be a better way to go about this, but perhaps this is just a bottom-up vs top-down thing. Anyway, I suspect that a DEP that boils down to “merge django-two-factor-auth into Django” and includes integration with arbitrarily chosen commercial services would face some resistance; you need more structure and details.

I hope this helps,

-- 
Aymeric.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.
Message has been deleted

m.lev...@web.de

unread,
Jun 13, 2017, 3:37:01 PM6/13/17
to Django developers (Contributions to Django itself)
I would like to explain a potential solution I have been working on (See commit https://github.com/mlevental/django/commit/51dbaa6748076e06d91b361c2fa60ecf24f5c27e). I think it's not complete but I don't have the time to continue working on it.

Overview:
  • In order to check if a user is authenticated with one or two factors, the attributes is_one_factor_authenticated and is_two_factor_authenticated were added to AbstractBaseUser (and therefore to AbstractUser and User) and AnonymousUser.
  • For AnonymousUser both attributes are always False. For AbstractBaseUser is_one_factor_authenticated is always True because an authenticated user is authenticated with at least one factor, is_two_factor_authenticated is explained in the next paragraph.
  • is_authenticated stays unchanged but the meaning becomes "authenticated with one or two factors". This way an application can have users who use 1FA and others who enabled 2FA.

  • Similar to the backends in django.contrib.auth there are backends for 2FA. But instead of returning a User object they must return a Device object on success.
  • Device is a model class that encapsulates all the necessary data of a 2FA method for a single user, e.g. cryptographic keys. It can but doesn't have to represent a real device.
  • After a successful two factor authentication the device is stored in the session. The middleware TFAMiddleware (which must be installed after AuthenticationMiddleware) sets this device for the user coming from the request object.
  • is_two_factor_authenticated of AbstractBaseUser evaluates to True only if a device is set.

  • The authentication process is handled by two views. FirstFactorLoginView is responsible for authenticating with the first factor and redirecting to SecondFactorLoginView on success. SecondFactorLoginView handles the authentication with the second factor and redirects to a defined URL on success. If the user accessing the SecondFactorLoginView isn't authenticated with the first factor he is redirected to the FirstFactorLoginView.
  • The SecondFactorLoginView can have multiple forms.
    • This is done because different 2FA methods can require different input fields (e.g different input types, labels, number of input fields).
    • Also this allows to display multiple forms, for example when it is desired to show the form for the default 2FA method and the backup method on one page.
    • By default SecondFactorLoginView loads all the forms specified in the setting TFA_FORMS. Which forms are displayed can be adjusted in the template or by overriding get_form_classes(). Only one form gets validated on a POST request. Therefore when submitting the form the 2FA method name needs to be included as a HTTP POST parameter.
    • For example if the setting TFA_FORMS is the following:
        TFA_FORMS = [
           
{'METHOD_NAME': 'TOTP', 'FORM_PATH': 'django.contrib.twofactorauth.forms.TOTPAuthenticationForm'},
           
{'METHOD_NAME': 'Backup Token', 'FORM_PATH': 'django.contrib.twofactorauth.forms.BackupTokenAuthenticationForm'},
       
]
                  
                  and the TOTPAuthenticationForm is submitted, then type=TOTP must be included, for example in a button tag:

       <form method="POST">
          {% csrf_token %}
          {{ forms.TOTP.as_p }}
         
<button name="type" value="{{ forms.TOTP.method_name }}">{% trans 'Submit' %}</button>
       
</form>

  • For convenience there is the tfa_required decorator and the mixin TFARequiredMixin. They work analogous to the login_required decorator and LoginRequiredMixin from django.contrib.auth. Instead of checking for is_authenticated they check for is_two_factor_authenticated. If the user is not two factor authenticated he is redirected to a specified URL.
  • For example the URL can be specified by setting TFA_LOGIN_URL to "tfa:first_factor_login" and would point to the FirstFactorLoginView. By default the FirstFactorLoginView requires the user to authenticate with the first factor and redirects on success to SecondFactorLoginView. If desired the FirstFactorLoginView can redirct an already one factor authenticated user to SecondFactorLoginView right away, for this redirect_authenticated_user needs to be set to True. After successfully authenticating with the second factor the user is redirected to the initially inaccessible page. For this last redirect to work the redirect URL is transfered from FirstFactorLoginView to SecondFactorLoginView by appending it as a HTTP GET parameter.

  • For the admin site to support 2FA the class AdminSiteTFARequired was created which inherits from AdminSite and the new mixin AdminSiteTFARequiredMixin. This mixin overrides has_permission() so that admin urls can only be accessed by two factor authenticated users. login() is overriden to call the correct view depending on the authentication status of the user: requested admin url or FirstFactorLoginView or SecondFactorLoginView.


Incomplete or missing features:
  • With this solution access to views can be granted only to users who are two factor authenticated. But there is no view, decorator or mixin for the case when a view should be accessible to both users who wish to use 1FA and users who use 2FA.
    • The current login required decorator and mixin can't be used for this because they only check for is_authenticated and thus they will also let in users who have setup 2 factors but are authenticated with only 1.
    • The naive way to do this would probably be to check whether the non-authenticated user needs to authenticate with 1 or 2 factors and redirect to the according login view (LoginView or FirstFactorLoginView). But the check can't be performed for non-authenticated users because they are instances of the class AnonymousUser. So the check needs to be performed when the user is authenticated with at least the first factor.
    • some helper methods/views were implemented:
      • has_tfa_enabled(user) returns True if the user has at least one device. This will return False for AnonymousUser.
      • is_tfa_required(user) returns True if either 2FA is forced for every user or 2FA is optional and the user has enabled 2FA. This will return False for AnonymousUser if 2FA is optional.
      • TFADisableView is a view for disabling 2FA. Disabling means deleting all devices for the user. This way has_tfa_enabled(user) will return False.
  • TOTP can be used as a 2FA method but the setup/registration is not implemented. For TOTP to work the client needs to receive some configuration data. This data is usually displayed as a QR code and is scanned with an app like Google Authenticator.
    • In the class TOTPDevice the configuration data is accessible via the property config_url. Generating the QR code from config_url works for example with the library qrcode.
Reply all
Reply to author
Forward
0 new messages