This Is How You're Going To Screw Up Two-Factor Auth


HeapSource recently released a Ruby gem to help web app developers perform "Effortless Two-Factor Authentication in Rails". While there's nothing wrong with this code per se, the idea that developers can "effortlessly" add 2FA to their apps is somewhere between ignorant and dangerous.

If you're a developer, I don't have to tell you that there are very few libraries that "just work" when you drop them in to your app. Library code is often just the 20% of the iceberg poking out over the water; all the boring support code to integrate it into your app (you know, the part you have to write) is 80% of the total effort.

Image by Uwe Kils

Image by Uwe Kils

But with authentication and authorization code, that "boring stuff" is not only hard to get right, but crucially important to building a usable and secure system. For example, you might choose to store your users' passwords using a cool piece of cryptographic technology like the bcrypt library (1,785 lines of C), but you'll need a large library like devise (9,567 lines of Ruby, plus your own glue code) on top of bcrypt to implement the workflows and policies that make up a working authentication system.

In this post, I'll take a look at the "boring stuff" that's required to build a 2FA system on top of a one-time-password (OTP) library like the one that HeapSource has released, and show you how easy it is to get those things wrong (even if you're a massive tech company).

Building 2FA on top of OTP

The code that HeapSource has made available is a Rails implementation of the RFC 6238, the Time-based One-Time Password algorithm (TOTP). This is a cryptographic algorithm for allowing a client (your user) and server (your app) to generate codes using a shared secret key that are only good for 60 seconds. If you've used any kind of two-factor authentication system, you've seen these six- or eight-digit codes generated by an authenticator app, or sent to your phone via SMS. The gem also includes a QR-code generator that can be used at setup time to transmit the private key from the server to the client.

Given these primitives (secret generation, a QR code, and a validation algorithm), you theoretically have everything you need to start authenticating your users with a second factor, right? Let's look at some real-world 2FA systems to see what else you'll need to build:

  • Enrollment: You need to activate 2FA for your existing users, which means generating a secret key and getting it to them (more on this later). Will 2FA be optional or required? If it's optional, how will non-2FA users upgrade their accounts? How will your app keep track of which users are enabled for 2FA, and which aren't?
  • Device deployment: How do users obtain their codes? You can deliver codes via an app like Google Authenticator, but that requires your user to have a smartphone, so you'll probably want to deliver codes via SMS as well. For example, Facebook's 2FA encourages users to use SMS in order to avoid having to maintain two different deployment modes.
  • Group Policy: If your app is used by teams or companies, you may want to give the team administrator control over whether or not her team can use 2FA (or must use it). In Google Apps, admins can forbid, permit, or require 2FA for user accounts. If you're going to require it, again, you'll have to migrate existing users to activate a second factor. And of course you'll have to educate users about the new restrictions their administrator is putting on them.
  • Mobile phone verification: You're probably going to be sending SMS; how are you going to verify that the user owns the phone you're sending to? You'll need a flow similar to proving email address ownership. If a user has to change her phone number, you'll also need to securely validate the change and the new device. When Twitter rolled out 2FA earlier this year, they re-used their existing SMS system, which led to confused and locked-out users.
  • Device Revocation: What happens when you lose your phone? You'll need to revoke the OTP secret on the old device, and provision a new one. Of course, you want to do this in a way that prevents an attacker from "faking" a lost phone. Hackers used a flaw in Dropbox's email verification to bypass its 2FA system.
  • Second factor temporarily unavailable (aka dead phone battery): Google provides printable "backup codes" that you can use if your phone is inaccessible or you can't receive SMS - you'll need some way to prevent locking users out completely in that case.
  • Session management: When are you going to ask for an OTP code? On every login? If so, how long will your sessions last? Google requires an OTP code every 30 days, or if you're using a new device (are you going to implement device registration?) - Facebook requires a code when "something suspicious" happens. Another strategy is to use so-called "step-up" authentication, where the 2FA code is required when you're about to perform a more sensitive operation, but not for basic usage.
  • Legacy apps: Does your application have an API? How do apps authenticate to that API? You can provide "application-specific passwords" to legacy clients, but there's danger that those passwords can be used for privilege escalation, as Google learned a few months ago.

Don't Do It Yourself

Everyone hates passwords, and there are great reasons to add 2FA to your authentication system. But it's hard. Really hard. And while you have to get the crypto right, that's ultimately a small part of building a working 2FA system.

If you're building a web application, you should have strong authentication for your web app, and the best way to do so is to outsource it to someone who knows what they're doing. And if you really must be your own identity provider, we recommend you use a service provider like Authy to implement 2FA for you. You could spend your time building a comprehensive authentication system (and you might even get it right), or you could spend your time working on your app. Which is more valuable to your users?