Implementing Google OTP-Based 2FA in a Golang Gin Framework System

Implementing Google OTP-Based 2FA in a Golang Gin Framework System

Two-factor authentication (2FA) adds an extra layer of security to the authentication process by requiring users to provide two forms of identification. In this tutorial, we'll implement Google OTP-based 2FA in a Golang Gin framework system.

Prerequisites

Before we start, ensure you have Go installed on your machine. You can download it from the official Go website.

Step 1: Install Required Packages

First, install the necessary packages:

go get github.com/dgryski/dgoogauth
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/sessions
go get github.com/gin-contrib/sessions/cookie

Step 2: Basic Setup

Set up a basic Gin application with session management.

package main

import (
"github.com/gin-gonic/gin"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"

)

func main() {
r := gin.Default()

store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("mysession", store))

r.GET("/login", showLoginPage)
r.POST("/login", performLogin)
r.GET("/2fa", show2FA)
r.POST("/2fa", perform2FA)

r.Run(":8080")
}

Step 3: Handling Login

Create the login endpoint that handles the initial username and password authentication.

func showLoginPage(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", gin.H{})
}

func performLogin(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

// Simple user authentication for demonstration purposes
if username == "user" && password == "password"
{
session := sessions.Default(c)
session.Set("user", username)
session.Save()

c.Redirect(http.StatusFound, "/2fa")
return
}

c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"error": "Invalid username or password",
})
}

Step 4: Implementing 2FA

After successful login, redirect users to the 2FA page where they enter the OTP generated by Google Authenticator.

import (
"net/http"
"github.com/dgryski/dgoogauth"

)

var otpConfig = &dgoogauth.OTPConfig{
Secret: "your-secret-key",
WindowSize: 3,
HotpCounter: 0,
}

func show2FA(c *gin.Context) {
c.HTML(http.StatusOK, "2fa.html", gin.H{})
}

func perform2FA(c *gin.Context) {
code := c.PostForm("code")

ok, err := otpConfig.Authenticate(code)
if err != nil {
c.HTML(http.StatusInternalServerError, "2fa.html", gin.H{
"error": "Error validating OTP",
})
return
}

if ok {
session := sessions.Default(c)
session.Set("authenticated", true)
session.Save()

c.Redirect(http.StatusFound, "/dashboard")
return
}

c.HTML(http.StatusUnauthorized, "2fa.html", gin.H{
"error": "Invalid OTP code",
})
}

Step 5: Creating Protected Routes

Ensure that users who have successfully passed 2FA can access protected routes.

func dashboard(c *gin.Context) {
session := sessions.Default(c)
authenticated := session.Get("authenticated")

if authenticated == nil || !authenticated.(bool) {
c.Redirect(http.StatusFound, "/login")
return
}

c.HTML(http.StatusOK, "dashboard.html", gin.H{})
}

Final Steps

Create the necessary HTML files (login.html, 2fa.html, dashboard.html) to render the pages. Here’s an example of what login.html might look like:

<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
<input type="text" name="username" placeholder="Username" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>

Similarly, create 2fa.html and dashboard.html to handle 2FA input and display the dashboard, respectively.

Sequence Diagram

Conclusion

In this tutorial, we implemented a simple two-factor authentication system using the Gin framework in Golang and Google OTP. While this example is simplified for demonstration purposes, in a production environment, ensure to use more secure practices, such as securely storing secrets and handling errors more robustly. Two-factor authentication significantly enhances security by ensuring that only users who possess the second factor (OTP) can gain access, thereby protecting against compromised credentials.

Read more