Two factor auth using the TOTP draft spec. Compatible with the Google Authenticator or any other TOTP token. (Unmaintained, last updated in 2013.)
Latest commit ecedc06 Oct 14, 2013 @mtigas note



© 2011-2013 Mike Tigas (@mtigas)
MIT License

A django app that allows supplementing the default django.contrib.auth login system with two-factor authentication using the Time-Based One-Time Password (TOTP) Algorithm spec (RFC 6238).

Google Authenticator is currently the primary support target for tokens, but any TOTP (soft?) token that allows using a server-generated seed should work.

Some notes:

Possible alternatives

This project hasn't been touched in a while, but the proof of concept still works. Other similar projects may have more fleshed-out APIs and integration:

Current status

This app is a work in progress. It is not much farther than "proof of concept," however. Clone the source and look at twofactor_demo/README.mdown for a quick demo (including steps on how to set it up).

The basics work:

  • If the special adminsite subclass is used, all login forms are replaced with one that has an optional "Auth Code" field. (Users with two-factor auth enabled will be required to enter this.)
  • In the admin, a "Two-factor Authentication" link shows up next to the "Change password" link. From here, a user may enable two-factor auth (if it is not enabled), reset their auth token (to allow migration to a new device), or disable two-factor auth (if it is enabled).

What does not work:

  • (Optional) backup codes or some ability to reset your auth token (or even simply log in) if you have lost your token. Optional because in some usecases this is a feature for high security that require manual intervention (support call to have a human verify and reset). FWIW, Google does provide this in the form of temporary, one-time-use codes that are received when two-factor authentication is turned on.


Optionally, for performance:

  • PyCrypto -- See PyCrypto section below, under Security Considerations.

Security Considerations

Section 5.1 of RFC 6238 "recommends" that the shared secret key (i.e. the "seed" for the time-based generator) be stored securely in the validation system (i.e., the server) and specifies encrypting the values until their use is necessary.

The key cannot be hashed (as with a password) because the raw value of the key is required to seed the generator. A single round of AES against settings.SECRET_KEY is used (with a randomly-generated seed appended to SECRET_KEY) as the encryption passphrase so that the value can be decrypted at validation time.

This works sort of like this:

stored_seed = salt + "$" + hexlify(AES(
    key = sha256(SECRET_KEY + salt),
    value = raw_seed
# i.e. 'CYM5yCSZ9Ybyu1dq$6cc094ca2e1eb46122d84ae877fff885'

This isn't perfect, but it obscures the values in the database in the event the database is compromised but settings.SECRET_KEY has not been.


A copy of pyaes is bundled with this app. If PyCrypto is installed, that library's C-compiled extensions will be used instead of the (extremely slow) native Python AES implementation.