Golang : Verify token from Google Authenticator App


Tags : golang qr-codes verify google-authenticator two-factor-authentication

One of the steps to implement 2FA(two-factor authentication) is to verify the token generated by app such as Google Authenticator App or Authy (https://www.authy.com/).

Once you have successfully scanned the QR code, an entry for SocketLoop will appear in the Google Authenticator App.

The next step is to use the https://github.com/dgryski/dgoogauth package to verify the token. The code below will show you how to generate the QR code that you need to scan the PNG file each time you run the code.( this is because the secret string will be changed each time ). In real world application, you just have to store the secret string into database and associate the secret string with the user that enabled 2FA.

 package main

 import (
 "bufio"
 "bytes"
 "code.google.com/p/rsc/qr"
 "crypto/rand"
 "encoding/base32"
 "fmt"
 "github.com/dgryski/dgoogauth"
 "github.com/disintegration/imaging"
 "image"
 "os"
 "runtime"
 "strings"
 )

 func randStr(strSize int, randType string) string {

 var dictionary string

 if randType == "alphanum" {
 dictionary = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 }

 if randType == "alpha" {
 dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 }

 if randType == "number" {
 dictionary = "0123456789"
 }

 var bytes = make([]byte, strSize)
 rand.Read(bytes)
 for k, v := range bytes {
 bytes[k] = dictionary[v%byte(len(dictionary))]
 }
 return string(bytes)
 }

 func main() {

 // maximize CPU usage for maximum performance
 runtime.GOMAXPROCS(runtime.NumCPU())

 // generate a random string - preferbly 6 or 8 characters

 randomStr := randStr(6, "alphanum")
 fmt.Println(randomStr)

 // For Google Authenticator purpose
 // for more details see
 // https://github.com/google/google-authenticator/wiki/Key-Uri-Format
 secret := base32.StdEncoding.EncodeToString([]byte(randomStr))
 fmt.Println(secret)

 // authentication link. Remember to replace SocketLoop with yours.
 // for more details see
 // https://github.com/google/google-authenticator/wiki/Key-Uri-Format
 authLink := "otpauth://totp/SocketLoop?secret=" + secret + "&issuer=SocketLoop"

 // Encode authLink to QR codes
 // qr.H = 65% redundant level
 // see https://godoc.org/code.google.com/p/rsc/qr#Level

 code, err := qr.Encode(authLink, qr.L)

 if err != nil {
 fmt.Println(err)
 os.Exit(1)
 }

 imgByte := code.PNG()

 // convert byte to image for saving to file
 img, _, _ := image.Decode(bytes.NewReader(imgByte))

 err = imaging.Save(img, "./QRImgGA.png")

 if err != nil {
 fmt.Println(err)
 os.Exit(1)
 }

 // everything ok
 fmt.Println("QR code generated and saved to QRimgGA.png. Please scan the QRImgGA.png with Google Authenticator App.")
 fmt.Println("NOTE : You need to remove the old entry for SocketLoop in Google Authenticator App each time.")

 tokenReader := bufio.NewReader(os.Stdin)

 fmt.Print("Please enter token to verify : ")

 // prompt user for input
 token, err := tokenReader.ReadString('\n')

 if err != nil {
 fmt.Println(err)
 os.Exit(1)
 }

 fmt.Println("Token : ", token)

 // setup the one-time-password configuration.
 otpConfig := &dgoogauth.OTPConfig{
 Secret: strings.TrimSpace(secret),
 WindowSize:  3,
 HotpCounter: 0,
 }

 // get rid of the extra \n from the token string
 // otherwise the validation will fail
 trimmedToken := strings.TrimSpace(token)

 // Validate token
 ok, err := otpConfig.Authenticate(trimmedToken)

 if err != nil {
 fmt.Println(err)
 os.Exit(1)
 }

 fmt.Printf("Token string [%s] validation is : %v \n", trimmedToken, ok)

 fmt.Println("IMPORTANT : Once the user token is validated. Store the secret string into")
 fmt.Println("database and memory. Use the secret string associated with this")
 fmt.Println("user whenever 2FA is required.")
 fmt.Println("If the user decides to disable 2FA, remove the secret string from")
 fmt.Println("database and memory. Generate a new secret string when user")
 fmt.Println("re-enable 2FA.")

 }

Sample output :

lYC4U9

NRMUGNCVHE======

QR code generated and saved to QRimgGA.png. Please scan the QRImgGA.png with Google Authenticator App.

NOTE : You need to remove the old entry for SocketLoop in Google Authenticator App each time.

Please enter token to verify : 230168

Token : 230168

Token string [230168] validation is : true

IMPORTANT : Once the user token is validated. Store the secret string into

database and memory. Use the secret string associated with this

user whenever 2FA is required.

If the user decides to disable 2FA, remove the secret string from

database and memory. Generate a new secret string when user

re-enable 2FA.

The above code is suitable for mobile app or IoT(Internet of Things) devices. If you are looking to implement 2FA for your website with Golang. Here's the code :

 package main

 import (
 "bytes"
 "code.google.com/p/rsc/qr"
 "crypto/rand"
 "encoding/base32"
 "fmt"
 "github.com/dgryski/dgoogauth"
 "github.com/disintegration/imaging"
 "image"
 "net/http"
 "os"
 "strings"
 )

 func randStr(strSize int, randType string) string {
 var dictionary string

 if randType == "alphanum" {
 dictionary = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 }

 if randType == "alpha" {
 dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 }

 if randType == "number" {
 dictionary = "0123456789"
 }

 var bytes = make([]byte, strSize)
 rand.Read(bytes)
 for k, v := range bytes {
 bytes[k] = dictionary[v%byte(len(dictionary))]
 }
 return string(bytes)
 }

 // need this to be global variable
 var secret string

 func Verify(w http.ResponseWriter, r *http.Request) {

 if r.Method == "POST" {
 token := r.FormValue("token")

 // setup the one-time-password configuration.
 otpConfig := &dgoogauth.OTPConfig{
 Secret: strings.TrimSpace(secret),
 WindowSize:  3,
 HotpCounter: 0,
 }

 trimmedToken := strings.TrimSpace(token)

 // Validate token
 ok, err := otpConfig.Authenticate(trimmedToken)

 // if the token is invalid or expired
 if err != nil {
 w.Write([]byte(fmt.Sprintf("<html><body><h1>Token [%s] verification : %v</h1></body></hmtl>", token, ok)))
 }

 // token validated and proceed to login, bla, bla....
 w.Write([]byte(fmt.Sprintf("<html><body><h1>Token [%s] verification : %v</h1></body></hmtl>", token, ok)))

 }

 }

 func Home(w http.ResponseWriter, r *http.Request) {

 //w.Write([]byte(fmt.Sprintf("Generating QR code\n")))

 // generate a random string - preferbly 6 or 8 characters
 randomStr := randStr(6, "alphanum")

 // For Google Authenticator purpose
 // for more details see
 // https://github.com/google/google-authenticator/wiki/Key-Uri-Format
 secret = base32.StdEncoding.EncodeToString([]byte(randomStr))
 //w.Write([]byte(fmt.Sprintf("Secret : %s !\n", secret)))

 // authentication link. Remember to replace SocketLoop with yours.
 // for more details see
 // https://github.com/google/google-authenticator/wiki/Key-Uri-Format
 authLink := "otpauth://totp/SocketLoop?secret=" + secret + "&issuer=SocketLoop"

 // Encode authLink to QR codes
 // qr.H = 65% redundant level
 // see https://godoc.org/code.google.com/p/rsc/qr#Level

 code, err := qr.Encode(authLink, qr.H)

 if err != nil {
 fmt.Println(err)
 os.Exit(1)
 }

 imgByte := code.PNG()

 // convert byte to image for saving to file
 img, _, _ := image.Decode(bytes.NewReader(imgByte))

 err = imaging.Save(img, "./QRImgGA.png")

 if err != nil {
 fmt.Println(err)
 os.Exit(1)
 }

 // in real world application, the QRImgGA.png file should
 // be a temporary file with dynamic name.
 // for this tutorial sake, we keep it as static name.

 w.Write([]byte(fmt.Sprintf("<html><body><h1>QR code for : %s</h1><img src='http://localhost:8080/QRImgGA.png'>", authLink)))
 w.Write([]byte(fmt.Sprintf("<form action='http://localhost:8080/verify' method='post'>Token : <input name='token' id='token'><input type='submit' value='Verify Token'></form></body></html>")))
 }

 func main() {
 http.HandleFunc("/", Home)
 http.HandleFunc("/verify", Verify)

 // this is for displaying the QRImgGA.png from the source directory
 http.Handle("/QRImgGA.png", http.FileServer(http.Dir("./")))

 http.ListenAndServe(":8080", nil)
 }

Hope you will find this tutorial useful in improving online security for your website or services users.

References :

https://www.socketloop.com/tutorials/golang-read-input-from-console-line

https://github.com/dgryski/dgoogauth

  See also : Golang : Generate QR codes for Google Authenticator App and fix "Cannot interpret QR code" error



Tags : golang qr-codes verify google-authenticator two-factor-authentication

By Adam Ng

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement