Implementing Two-Factor Authentication for Dummies

What is Two-Factor Authentication and why you should be interested

It is no surprise that security is a hot topic when building a modern web application. As the CEO of Ashley Madison could probably tell you, having your account stolen is not only annoying, it can be downright embarrassing. Having only a password is no longer the secure option it used to be. Users are vulnerable to social engineering or simple carelessness if they leave passwords on sticky notes (usually attached to their monitors). A password can also be revealed through the use of brute-force techniques by hackers. Therefore it’s no longer a question of should Two-Factor Authentication be implemented in your web application, but rather when.

 

hacking

 

For those unfamiliar with the term, Two-Factor Authentication (“2FA” for short) is an extra layer of security added to the login process. Users first enter their usernames and passwords, and if they are valid the application will prompt for a Two-Factor Authentication code which can be found on a device carried on the person — nowadays, this usually means the user’s smartphone. Physical 2FA devices exist, such as the RSA SecureID keyfob, but they are less common in the workplace. 2FA authentication codes are 6 digits long, and can be safely generated by authentication apps like Google Authenticator and Authy. The passcode is dynamic and a new one is generated every 30 seconds, meaning you must have physical access to the device in order to retrieve the code.

 

Where to start?

In order to use 2FA, users must first connect an authenticator app, either by entering a code or scanning a QR code with an authentication app. Thankfully, Google has a link developers can easily use in their application code to create a QR code for a secret key. This secret key is a unique 16-character base32-encoded value saved on your user model in your application database. It will be used later to generate a dynamic 6-digit code used to authenticate that user.

This is how a typical QR code link would be constructed:

            <img src="https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/{{ label }}?secret={{ secret_key }}"/>

 

There are two parameters in the URL that should be noted: the label and the the secret key. The label will be whatever is displayed as a title for the key on the user’s 2FA code. It can be a combination of your product name and the user’s email address, or the user’s username (ie, sendwithus:marie@sendwithus.com). Once a secret key has been generated for your user and a QR code has been displayed in the app to be scanned, you’ll need to implement an algorithm to generate 2FA codes based on a formula. This is the most complex part of the implementation.

At sendwithus, we use the TOTP (Time-based One-time Password) algorithm when implementing 2FA. It is a fairly straightforward formula using the stored secret key and the current timestamp to generate a 6-digit code. The user’s authentication app will use the same TOTP algorithm, but on the authentication device. This ensures the authentication code generated by your web application and the code displayed on Authy or Google Authenticator will match.

The steps are as such:

  1. First, generate a secret key (a unique 16-character base32-encoded code) and save it on your user object (or somewhere secure in your database). This should be the same secret key you use to generate the QR code for the user to scan.
  2. Generate a timestamp to use in the algorithm. As an aside, you probably want to use the current timestamp. Most products will also repeat the algorithm with one or two older timestamps, to allow for some delay between when the user reads their code, and when they actually enter it into your application.
  3. Decide which hash method to use. By default, TOTP uses SHA1 so that’s what we decided to use.
  4. Generate a 6-digit authentication code using the TOTP algorithm below.
            #turns a unix timestamp into a byte string

            bytetime = self._time_to_byte_string(timestamp)

            hmac_hash = hmac.new(

                base64.b32decode(secret_key, casefold=True),

                bytetime,

                hashlib.sha1,

            ).digest()

            # Generate code from hmac

            offset = ord(hmac_hash[19]) & 0xf

            code = ((ord(hmac_hash[offset]) & 0x7f) << 24 |

                    (ord(hmac_hash[offset + 1]) & 0xff) << 16 |

                    (ord(hmac_hash[offset + 2]) & 0xff) << 8 |

                    (ord(hmac_hash[offset + 3]) & 0xff))

            

            # '6' is number of integers in the OTP

            return code % 10 ** 6

(code snippet written by Joel Franusic @ Twilio)

 

Implementing SMS Authentication

Once that’s been done, there is also the option of implementing SMS authentication thanks to the Twilio API. This means that a user can, after enabling 2FA, enter their phone number, verify it by receiving a code through a text, and then use SMS going forward to receive a 2FA code instead of going through an authenticator app. Some products lets you enable two-factor authentication only with SMS and therefore not needing an authentication app, but we found this use case rare and not as secure. At Sendwithus, we made SMS an alternative option. If this is the route you plan to take, you will need to create a Twilio account and buy a phone number (around 1$/month). I am using Twilio here as this is the one I am most familiar with but any SMS API will do the trick.

Implementation looks like this:

  1. When a user enters their phone number, generate a code to send them for verification (doesn’t have to be a 6-digit 2FA code).
  2. Send the code in a SMS message (see code below)
  3. When the user enters the code, verify it’s the correct one.
  4. Only then, save the phone number on the user object.

Verifying phone numbers before hand eliminates the case when a user enters an invalid phone number and realizes it only later when they try to log back in but the SMS API returns an error message.

Here is a code snippet to send SMS through Twilio API (using Python and the requests library):


            # twilio_accound_sid and twilio_auth_token are your Twilio API Credentials
            path = 'https://%s:%s@api.twilio.com/2010-04-01/Accounts/%s/Messages.json' % (twilio_account_sid, twilio_auth_token, twilio_account_sid)

            to = '%s' % customer_phone_number

            # twilio_phone_number bought through Twilio
            from = ‘+%s’ % twilio_phone_number

            # Body message containing the verification code
            body = ‘Your verification code is %s’ % code

            data = {
                'To': to,
                'From': from,
                'Body': body
            }

            response = requests.post(path, data=data)

            if response.status_code != 200 and response.status_code != 201:
                raise GenericError("Error sending sms")

 

The Final Product

At the very end, your application login process needs to be modified to allow 2FA authentication. Every web application is different and therefore your implementation may differ from ours.Regardless of your workflow, there are certain questions you should be sure to consider. For example, “How long should an user be allowed to linger on this second step?”. Meaning, is a user allowed to enter their username/password and leave their desk with this open. Should this step timed out after a couple of minutes to avoid someone coming in and trying codes? To handle this, we chose to require that users provide a valid 2FA code within 60 seconds of entering their password to improve security.

Screen Shot 2016-03-03 at 3.05.19 PM

 

What about recovery codes?

Recovery codes are a list of one-time codes generated when you turn on 2FA. A user then saves them somewhere safe on their computer or prints them off to have a hard copy. They allow access to your app in case the user loses (or breaks) their phone. It’s important that recovery codes can only be used once. Otherwise, a user might use it as a second password which would defeat the purpose of two-factor authentication (especially with autofill turned on). The list of recovery codes should be stored securely, similar to the user’s secret key.

 

Technical Pitfalls

Unfortunately, 2FA is not impossible to crack. An even better system would be three-factor authentication with biometrics verification (retina scans, voice recognition, fingerprint scans, …). However, for most applications 2FA is a smart and secure feature to implement.

I also didn’t cover any of the database implementation details. That’s up to your team and you to decide how the data should be stored and encrypted. You should also go over how this data will be communicated between your database, the backend and frontend as you will have to ultimately pass the secret key to show the QR code to the user. Again of course, those solutions are dependent on your application.

 

Closing Notes

I hope this guide helps you in your implementation of 2FA. If you still don’t feel comfortable, there are many TOTP libraries that you can use. You may also use Authy API to handle 2FA for you. However, coming from someone who just implemented 2FA for the Sendwithus platform, it’s much simpler than it seems at first. Go and give it a try!

Share this post
Tweet about this on TwitterShare on Facebook

2 responses to “Implementing Two-Factor Authentication for Dummies”

  1. Jen says:

    This is so confusing to the average computer user. 🙂 So, I was on a site that I have a membership to. On the front page it said you must implement the 2FA. I clicked that button. I am now locked out. I have a significant amount of funds lock in that account. The thing is, I had not generated, copied or requested this. I thought it would walk me through steps or something. I can’t even write to support. I am locked on that page. I have the APG app on my phone. Each time I try to log on I believe there is a new pgp message. I emailed APG, no response. Can you PLEASE tell me how to find the decrypted message to get back in? I successfully decrypted a message but I copied and pasted the answer in the blank and it said “bad”. Please help!

    • Dylan Moore says:

      Hey Jen, I recommend trying to find contact info for the site you’re trying to access and emailing them directly.

Leave a Reply

Your email address will not be published. Required fields are marked *