How to add Google Authenticator support to your website

06 Jul 2023

06 Jul 2023 by Luke Puplett - Founder

Luke Puplett Founder

For Developers in a Hurry

Here's what it looks like in Zipwire but how is it done? Well, there's actually very little to it.

Let's get straight down to business. You know what Two Factor is and you know what the Google Authenticator app is but you need to implement it on your website and it looks complicated. No one will help on StackOverflow because any help will be removed for security reasons, leaving you stuck.

Thou shalt not copypasta

Okay, so first the small print.

Do not copy past code from forums because this is a critical security thing and you're putting your customers at risk by using some random helpful girl's code. Yeah and don't think you can use an online QR code maker thing because you'll be leaking the master key. YOUR HOME MAY BE REPOSSESSED IF YOU DO NOT KEEP UP REPAYMENTS ON YOUR MORTGAGE OR OTHER LOAN SECURED ON IT.

How it works

In a nutshell,

  1. The 6-digit code is officially known as a Time-based One Time Password and the specification is in RFC 6238.

  2. The QR code actually holds a URL like otpauth://totp/YouAppName?secret=base32SecretKey

  3. The Google Authenticator app, or Authy or whatever, scans the QR code and reads the seed key entropy and then starts to produce keys every so often, usually 30 seconds.

  4. Your website or app must produce the same codes at the same time to match them up. It's first job is to create some random seed entropy and produce the QR code and show it.

  5. Don't store the seed key for the user until they've scanned it and confirmed their current 6-digit code. The code is derived from your system clock, so your web server and your user must have accurate clocks.

  6. I recommend that you encrypt the seed key in your database; if you're hacked and they make off with your seed table, then you're shit outta luck.

  7. The code derives from a HMACSHA1 hash of the user's seed key bytes and some bytes that come from a portion of the current time. That time-based portion of bytes is the total count of 30 second intervals that have happened since the beginning of Unix time, which is 1970.

  8. Hashing algorithms tend to work on bits, byte arrays, so you'll need to turn the count of intervals into a big-endian byte array.

  9. To be clear: it's just a hash of the user's secret seed key and the current time.

  10. The rest of the code is either base-32 to byte array conversions, or the standard way to turn some bytes into 6-digits.

  11. No I can't show you any code. Use ChatGPT to generate the code and ask it to comment everything. Use the knowledge I've given you here to inspect every single line and remove all extraneous stuff. Note that in my experience, ChatGPT can't seem to get the 6-digit conversion bit shifting logic right, so I have a reference implementation below, scroll down.

  12. It's normal to make a code "either side" of the current one to allow for differences in the clocks between your customer's device and your web server.

  13. To do this, you just take 1 away from the current count of "intervals since Unix time began", and add 1 on the other side and produce 3 sets of 6-digit codes and check that any match what the customer has.

  14. You then format the URL as above and use a local library for your language to turn it into a QR code.

  15. You should also display the raw base-32 seed because some auth apps can't scan QR codes.

That's it. It's actually remarkably simple when it's not obscured by jargon or math.

Snippets

Okay here's a few little snippets that can help. These are in C# but you should get the idea. This simply turns the count of 30 second intervals into a byte array.

byte[] counterBytes = BitConverter.GetBytes(count);

if (BitConverter.IsLittleEndian)
{
    Array.Reverse(counterBytes); // Ensure big-endian byte order
}
    

And this simply creates the hasher, initialized with your customer's secret seed key bytes, and hashes the bytes that we just created.

HMACSHA1 hmac = new(secretKey);

return hmac.ComputeHash(counterBytes);
    

Finally, this is the hardest part by far, you need to make a 6-digit number from the binary hash. This code was taken from the RFC itself and turned into C#.

I implore you to look at the RFC and see that this code is doing the same thing, only in C#.

https://datatracker.ietf.org/doc/html/rfc6238

const int Fifteen = 0x0F;
const int OneTwentySeven = 0x7F;
const int TwoFiftyFive = 0xFF;

int offset = hash[hash.Length - 1] & Fifteen;

int binaryCode = (hash[offset] & OneTwentySeven) << 24 |
                    (hash[offset + 1] & TwoFiftyFive) << 16 |
                    (hash[offset + 2] & TwoFiftyFive) << 8 |
                    (hash[offset + 3] & TwoFiftyFive);

int validationCode = binaryCode % (int)Math.Pow(10, digits);

return validationCode.ToString().PadLeft(digits, '0');
                

That's lovely and everything but what is Zipwire?

Zipwire Collect simplifies document collection for a variety of needs, including KYC, KYB, and AML compliance, plus RTW and RTR. It's versatile, serving recruiters, agencies, people ops, landlords, letting agencies, accountants, solicitors, and anyone needing to efficiently gather, verify, and retain documented evidence and ID.

Zipwire Approve is tailored for recruiters, agencies, and people ops. It manages contractors' timesheets and ensures everyone gets paid. With features like WhatsApp time tracking, approval workflows, data warehousing and reporting, it cuts paperwork, not corners.

For contractors & temps, Zipwire Approve handles time journalling via WhatsApp, and techies can even use the command line. It pings your boss for approval, reducing friction and speeding up payday. Imagine just speaking what you worked on into your phone or car, and a few days later, money arrives. We've done the first part and now we're working on instant pay.

Both solutions aim to streamline workflows and ensure compliance, making work life easier for all parties involved. It's free for small teams, and you pay only for what you use.

Learn more