paint-brush
How To Implement Google Authenticator Two Factor Auth in JavaScriptby@rajat-sr
31,755 reads
31,755 reads

How To Implement Google Authenticator Two Factor Auth in JavaScript

by RajatOctober 13th, 2019
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

How To Implement Google Authenticator Two Factor Auth in JavaScript in JavaScript. The tutorial is based on an open-source project called Enquirer. It lets you create stylish command line prompts that are user-friendly and easy to create. In this article, we’ll be implementing both the algorithms, using NodeJS (JavaScript).Generating a random buffer of data using the in-built NodeJS library and then encoding the data to encode the data.Generating HMAC-based OTPs will also be verifying the OTP, using our implementation implementation.

Company Mentioned

Mention Thumbnail
featured image - How To Implement Google Authenticator Two Factor Auth in JavaScript
Rajat HackerNoon profile picture

Google Authenticator is something that many of us use all the time but how many of us really understand how it works under the hood?

In this tutorial, I will demystify the magic behind Google Authenticator’s expiring OTP codes and show you how to add Google Authenticator support to your application from scratch, in JavaScript.

As part of Pesto’s career accelerator program, I got an opportunity to contribute to this amazing open-source project called Enquirer. It lets you create stylish command line prompts that are user-friendly and easy to create. At the time of writing this article, Enquirer has upwards of 4500 Github stars and is used by more than 7500 other repositories. 

One of the features I added to Enquirer was the ability to verify time-based one-time passwords, directly from command-line, generated by any two-factor authentication application, such as Google Authenticator, without the need of an internet connection.


Why Time-based OTPs?

Time-based OTP (TOTP) is an algorithm that factors in the current time to generate a unique one-time password.

In today’s age, it is a no-brainer that passwords alone can’t keep the bad guys out. You need an additional layer of security — a second factor.

There are many types of two-factor authentication systems, but for them to work, they must be both secure and user-friendly. TOTP ticks both boxes perfectly.

It’s secure because:

  • The password changes every n number of seconds (usually, 30 seconds), preventing eavesdroppers from using that same password later in the future if somehow they’re able to get hold of it.
  • The password may be generated by an app on the user’s phone, making it more difficult for an attacker to acquire the password, as the user’s phone is usually by his/her side.

It’s user-friendly because:

  • In mobile app implementations, the user only needs to launch the TOTP application and then key-in the TOTP that appears on-screen into the application
  • Unlike most mobile-based one-time passwords that need to be received via a text message or the Internet through some wireless connection, TOTPs aren’t dependent on the presence of a cellular signal or data connection.

I used Google Authenticator as the mobile app to verify one-time passwords.

Google Authenticator generates time-based OTPs which are calculated using the algorithm specified in RFC6238. The app also supports HMAC-based OTPs calculated using the algorithm specified in RFC4226

Time-based OTPs rely on the algorithm for HMAC-based OTPs (HOTPs). In this article, we’ll be implementing both the algorithms, using NodeJS (JavaScript).

Generating Secret

Firstly, we’ll have to create an application-specific secret key that will be used to verify OTPs. This will also be shared with the Google Authenticator app.

The secret key is how the authenticator apps know what OTPs to generate for a specific app/website. The secret can be generated in many ways and this is one of the implementations.

We’ll generate a completely random buffer of data using the in-built

crypto
NodeJS library and then we’ll encode the data to base32 using
hi-base32
npm module.

const crypto = require('crypto');
const base32 = require('hi-base32');

function generateSecret(length = 20) {
   const randomBuffer = crypto.randomBytes(length);
   return base32.encode(randomBuffer).replace(/=/g, '');
}

This function will return a random string which is our secret key. 

Input this in the Google Authenticator app. We’ll be verifying the OTPs for this secret, using our implementation.

Generating HMAC-based OTPs

To generate HOTP we need a secret key and a counter value. We already have a secret. We don’t need to worry about counter as of now because we’ll be providing its value when we generate TOTPs.

Basics
Let’s say our function that generates HOTP accepts two arguments,

secret
and
counter
. First, we’ll decode the secret.

const decodedSecret = base32.decode.asBytes(secret);

Now we’ll create a buffer from the

counter
value.

const buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
   buffer[7 - i] = counter & 0xff;
   counter = counter >> 8;
}

According to RFC4226, we have three major steps to generate a HOTP.

Step 1 — Generate an HMAC value
Google Authenticator uses SHA1 algorithm to create HMAC. We’ll use functions from the

crypto
library to create an HMAC (using SHA1), update the above-created
buffer
with this and then produce an HMAC value;

const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
hmac.update(buffer);
const hmacResult = hmac.digest();

hmacResult
is a 20-byte string which is an HMAC-SHA-1 value.

Step 2 — Dynamic Truncation
The purpose of dynamic truncation is to extract 4-byte dynamic binary code from a 20-byte HMAC-SHA-1 result. These steps are directly taken from RFC4226.

const offset = hmacValue[hmacValue.length - 1] & 0xf;
const code = ((hmacValue[offset] & 0x7f) << 24) |
             ((hmacValue[offset + 1] & 0xff) << 16) |
             ((hmacValue[offset + 2] & 0xff) << 8) |
             (hmacValue[offset + 3] & 0xff);

Step 3 — Compute the HOTP value
Google Authenticator generates six-digit passwords, so we’ll extract the first six-digits of the

code
to get our final HOTP value.

const hotp = code % (10 ** 6);

Putting it all together, the generateHOTP function can be written as:

const crypto = require('crypto');
const base32 = require('hi-base32');

function generateHOTP(secret, counter) {
   const decodedSecret = base32.decode.asBytes(secret);
   const buffer = Buffer.alloc(8);
   for (let i = 0; i < 8; i++) {
      buffer[7 - i] = counter & 0xff;
      counter = counter >> 8;
   }

   // Step 1: Generate an HMAC-SHA-1 value
   const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
   hmac.update(buffer);
   const hmacResult = hmac.digest();
 
   // Step 2: Generate a 4-byte string (Dynamic Truncation)
   const code = dynamicTruncationFn(hmacResult);

   // Step 3: Compute an HOTP value
   return code % 10 ** 6;
}

function dynamicTruncationFn(hmacValue) {
   const offset = hmacValue[hmacValue.length - 1] & 0xf;

   return (
      ((hmacValue[offset] & 0x7f) << 24) |
      ((hmacValue[offset + 1] & 0xff) << 16) |
      ((hmacValue[offset + 2] & 0xff) << 8) |
      (hmacValue[offset + 3] & 0xff)
   );
}

We have a function using which we can generate HMAC based one-time passwords. Now we can use this function to generate Time based one-time passwords.

Generating Time-based OTPs

The algorithm for time-based OTPs simply uses current Unix time (with a time-step of 30 seconds) as the

counter
value in the
generateHOTP
function.

We can get the current time in JavaScript using

Date.now()
. But this returns the number of milliseconds elapsed since
January 1, 1970, 00:00:00 UTC
. We have to convert the time-step of 1 millisecond to time-step of 30 seconds.

const counter = Math.floor(Date.now() / 30000);

We also accept a

window
argument which helps us get the TOTP of any time window from current time. So if we want to know what TOTP was generated before 2 minutes from now, we set
window = -4
. This calculates the TOTP at 4 time-steps (of 30 seconds each) before the current time.

So, our

generateTOTP
function can be written as:

function generateTOTP(secret, window = 0) {
   const counter = Math.floor(Date.now() / 30000);
   return generateHOTP(secret, counter + window);
}

Verifying Time-based OTPs

To verify TOTPs generated on the Google Authenticator app, we need the secret key. Once we have the secret key we can use the

generateTOTP
function above and calculate the TOTP to see if it matches or not.

Sometimes, it is possible that while a user is typing the OTP the current window of 30 seconds passes and the OTP she entered gets failed. To improve the user experience, we not only check the TOTP generated at the current time-step window but also one step before and one step after the current window. 

If the current time is T⁰, we'll verify the TOTP for these steps:
   T⁰ – 1
   T⁰ 
   T⁰ + 1

We can also use a wider window but doing so may be a security risk. If we use a window of

T⁰ ± 20
, for example, this would mean that an OTP generated 10 minutes ago is still valid!

Keeping this in mind, our

verifyTOTP
function can be written as:

function verifyTOTP(token, secret, window = 1) {
   if (Math.abs(+window) > 10) {
      console.error('Window size is too large');
      return false;
   }

   for (let errorWindow = -window; errorWindow <= +window; errorWindow++) {
      const totp = generateTOTP(secret, errorWindow);
      if (token === totp) {
         return true;
      }
   }

   return false;
}

token
is the TOTP from Google Authenticator. We are invoking
generateTOTP
function to calculate the TOTPs for all windows and checking if it matches with the token entered.

This function returns

true
if the token is successfully verified. This completes the implementation of Two Factor Authentication (TOTP) with Google Authenticator.

I’ve removed some error checking to keep this article simple. You can find the complete code at this pull request

Make sure you check out the Enquirer library and drop a Github star. Thanks for reading!

Hi, my name is Rajat. I am a truly full-stack software engineer with experience working on everything from patching U-boot and Linux drivers in C to launching top-rated products on Product Hunt.