How to create a TOTP 2FA code for your app

0

Full article

I've been using 2FA on every site that supports it for quite some time, but I've never given much thought to how a 2FA code is created. I enable it, scan the QR code, and print the backup codes. The rest is magic. 🧙‍♂️

But no more! Today is the day we figure out how to generate a 2FA code...

A few basics...

If you've used 2FA and you know what TOTP is, skip this section. For everyone else...

What is 2FA?

For the totally uninitiated it just means "two factor authentication", or a second way to authenticate yourself, often by generating a random code using an app on your phone. More generically, it means proving you are by providing a combination of something you have, something you know, something you are, etc.

Even if you don't think you've ever used 2FA before, you have...

  • Ever inserted an ATM card (have) and entered a PIN (know)?
  • Or inserted a credit card (have) and entered a zip code (know)?
  • Presented your insurance card (have) and verified your birth date (know)?
  • Shown your secret government id (have) and submitted to an eye scan (are)?

This stuff used to apply to relatively few things though - a bank account and a few credit cards. Now we have dozens (or hundreds) of logins, each of which secures a site that holds some of our private data. The theory remains though - the more ways you have to prove who you are, the less likely someone can pretend to be you.

There's a lot of ways to do 2FA, but some are more common than others...

  • Enter a password (know), and answer canned questions (know). (awful - check out how many known data breaches included security questions)
  • Enter a password (know), and answer custom questions (know). (meh)
  • Enter a pw (know), then a code that's texted to your phone (have). (not the best)
  • Enter a pw (know), then a randomly generated, one-time use code from your phone that expires in 30 seconds (have), aka TOTP.

What is TOTP?

A time-based one-time password (TOTP) is just one way to do two-factor authentication... but it's very common. A TOTP only works for a certain period of time (usually 30 seconds), and it only works once (so trying to log in twice with the same password should fail).

What's it look like?

A website generates a QR code for you to scan with an app like andOTP or LastPass Authenticator. The QR code represents a URI with a few pieces of data, including a random string that's unique to you, which is encoded in base32 (more on that later). From then on, the app generates a new random code every 30 seconds, which you use when you login. And in case your phone catches fire, they provide some recovery codes for you to print and file away. How nice. 🔥📱🔥

If someone guesses your password, or hacks your email and requests a password recovery, they still can't login unless they also have your phone. Now if they hacked the site and gained access to everyone's accounts, 2FA ain't gonna save anyone. But at least they can't reuse your password to login to other sites that have 2FA enabled.


How are they created?

For the user, it starts with a QR code that represents a URI. Use a service like ZXing Decoder on one of those QR codes, and you'll see it holds a few pieces of data, as outlined here: Key URI Format

Dissecting a QR code

Let's take a minute to break that down:

  • Type: The "totp" indicates this is a time-based one-time code.
  • Label: The "csinfotest" is a label used to identify the code on your phone.
  • Issuer: The "Namecheap - ...." param is also a label. (because reasons...)
  • Secret: A base32 encoded key that's unique for each user, and used in conjunction with the current time to generate the code your app shows you.

"But", you might say, "that Key URI Format page mentions other fields too!"

Yep, there are optional parameters; but when I tested a code that used them, the results were inconsistent. Google's and Microsoft's apps ignored the extra params, while andOTP and LastPass show an 8 digit number for 15 seconds, but not the same number. One of them might be ignoring the algorithm or something. 😵

For now, I'd suggest just setting the 4 basic fields, if you want your code to work everywhere. Also, take a moment to appreciate how the "label" and "issuer" values are used in each app to disambiguate which account the code is for. That's important if your users use a lot of 2FA codes, like I do.

What is base32 encoding?

Of the four pieces of data I mentioned, the one that makes this whole thing work is the base32 "secret" string, so let's review what base32 encoding / decoding is.

Basically, it means converting a string that could have any data in it to a string that's a set of 32 characters. The exact set of 32 characters can vary depending on the use case, but they're often selected for specific reasons, such as:

  • a legacy system that can't handle certain characters,
  • the need to be easily human readable (so not using O and 0, l and 1, etc),
  • use as a file name (so not \ or /)

If you're having trouble sleeping tonight, check out RFC 4648 concerning base16, base32, and base64, particularly section 3.4 on choosing the alphabet.

For a more practical example, let's check out a base32 encoder that uses [a-z0-9] except for the letters i, l, o, and s (for readability). Encode a string with non-ASCII characters, like "Écrire, c'est une façon de parler sans être interrompu.", and you'll get back a string like "t5hq4ubjcmp20rt7cntq883ndtjj0tk1wxqpw834cmg70rbjdhjq483kc5q7687aeht6a839dtu6awkjdxpq0x9e". Decode it to get back the original result. Try it out!

Generating your very own codes

Hopefully it's becoming apparent that creating your own 2FA code isn't that difficult. The important part is generating a good random string for each user (which you'll store), and being able to base32 encode it.

Looking at those 4 fields again, here's what we'll use:

  • Type: "totp"
  • Issuer: Your product name, like "Acme"
  • Label: The format "Product:Account Name", like "Acme:jdoe@gmail.com"
  • Secret: A random string or whatever you want (Google calls it an "arbitrary key value"), base32 encoded so that users who can't scan your QR code can still type the secret in manually.
otpauth://totp/${encodeURI(label)}?secret=${secret}&issuer=${encodeURI(issuer)};

Once you've got everything, you need a library that can convert it to a QR code. If you're using JavaScript, checkout the 2FA QR code generator which uses jQuery.qrcode. For C#, there's the QRCoder library. I'm sure libraries have been written for other languages too - don't try reinventing the wheel.

Your user scans the QR code, at which point their app and your system will both be storing the secret code - hopefully securely! No matter what 2FA app they used to scan the code originally, it should be capable of combining the secret with the current time and generating a single-use code that's good for 30 seconds.

Authenticating users (verifying TOTP)

The very last part of the process is validating that your user entered a good code. When they enter a 2FA code from their phone app, you'll need to retrieve their secret from your database and generate the same code according to a certain algorithm.

You can try to implement that algorithm yourself (not recommended) or just look for a reliable library in the language of your choice like JavaScript or Otp.NET in C#. And since the clock on your server and the user's phone may be slightly out of sync, it's possible that they'll get a code that's different than the one you calculate, so you'll want to calculate several codes (one for the previous 30 seconds, and one for the next 30 seconds).

If you'd like to see an entire sample implementation written in C#, I plan on making one in the next few days. (Update: You can check it out here.)

Author

Grant Winney

I write when I've got something to share - a personal project, a solution to a difficult problem, or just an idea. We learn by doing and sharing. We've all got something to contribute.



Comments