Go Lang JWT Authentication

Created At: 2023-05-28 04:37:43 Updated At: 2023-06-25 21:08:34

Here, we will see how to use Go language with JWT for authencation and project routes. Along the way, we will learn Gin framework, Go server, Routes and Token generation. Let's see what you will be building at the end of the tutorial. You may see the overall overview

Modules to build

Here we will build three modules.

  1. token controller
  2. user controller
  3. secured controller

First module would be about token controller. It would help us generate token. The second one would be user controller, with this we would be able to register users to the system. And the third one would be secured controller.

Secured controller would be projected by JWT.

We will also add some helpers for JWT that will assist us in Generating the tokens with proper expiration times and claims, and a way to Validate the sent tokens. This will be used by our custom Middleware to restrict access. Also, as mentioned earlier, in the registration process, we will be storing the user data in a MySQL database using the GORM Abstraction. 

We are going to use Gin framework to do framework to do everything. It's a great framework if you want faster developement with Go language. 

Let's first go and create go module. First create a folder and name it JWT. You may name it anything. Inside the module run

go mod init jwt-authentication-golang

With this we will see there's jwt-authentication-golang in your go.mod file. Of course go.mod would be auto generated after the above command has been executed.

And inside the same folder (root folder) run 

touch main.go

With the above command we will be creating a main.go file. You may name it anything too. Since we are going to use Gin framework, we need to install it. Let's run the below command

go get -u github.com/gin-gonic/gin

Gin a top notch framework among Go and you will love the simplicity of it.

Set up Database and Migrations

One of the very first thing you wanna do it, set up the database and create migrations. Creating migrations means creating database table in the database. 

In the root folder let's create a new folder name models and inside it create a file name user.go  and in the file put the code below

package models

import "github.com/jinzhu/gorm"

type User struct {
	gorm.Model
	Name     string `json:"name"`
	Username string `json:"username" gorm:"unique"`
	Email    string `json:"email" gorm:"unique"`
	Password string `json:"password"`
}

Let's see how it looks like so far

Since we are going to Gin framework, it provides ORM which is called GORM in this case. It means Gin's ORM. We are using gorm.Model property of it, so that we can get some extra features of Gin into the model like preventing duplicate user name and email. It becomes very handy.

We want once the above code is executed, then a table would be created using MySQL driver. So we need to install Go Lang MySQL driver and related packages. Let's run the below command to do it.

go get gorm.io/gorm

go get gorm.io/driver/mysql

The above command with install GORM packages and MySQL database driver. You need to run the command from the root of your project.

In the root of your project create a folder name database and and then create a file name client.go. In it, put the code below

package database
import (
	"jwt-authentication-golang/models"
	"log"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)
var Instance *gorm.DB
var dbError error
func Connect(connectionString string) () {
	Instance, dbError = gorm.Open(mysql.Open(connectionString), &gorm.Config{})
	if dbError != nil {
		log.Fatal(dbError)
		panic("Cannot connect to DB")
	}
	log.Println("Connected to Database!")
}
func Migrate() {
	Instance.AutoMigrate(&models.User{})
	log.Println("Database Migration Completed!")
}

Here we used Instance *gorm.DB to create a database instance and it would be used in our app globally.

Connect() function will create a mysql database connection and Migrate() will create a table name users if it already does not exist.

This is called creating migrations which is same as Laravel Migrations. Now our main.go looks like below

package main

import (
	"jwt-authentication-golang/database"
)

func main() {
	// Initialize Database
	database.Connect("root:root@tcp(localhost:3306)/jwt_projects?parseTime=true")
	database.Migrate()
}

Make sure that you import your module and package names correctly. Here first root is the user name and second root is the database password. You may change them as you wish.

If you run the below command

go run .

You will see

Which means that everything worked correctly so far. And if you check your database you will see that, you have users table created.

User Controller

Our next job is to create a user a controller and in the controller we will create methods for validation. Inside the user.go file, put the code below. The below code would be helpers for usercontroller.go. We are yet to create usercontroller.go

func (user *User) HashPassword(password string) error {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
	if err != nil {
		return err
	}
	user.Password = string(bytes)
	return nil
}
func (user *User) CheckPassword(providedPassword string) error {
	err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(providedPassword))
	if err != nil {
		return err
	}
	return nil
}

Now at the root of your project, create a folder name controllers and then create a file name usercontroller.go

package controllers
import (
	"jwt-authentication-golang/database"
	"jwt-authentication-golang/models"
	"net/http"
	"github.com/gin-gonic/gin"
)
func RegisterUser(context *gin.Context) {
	var user models.User
	if err := context.ShouldBindJSON(&user); err != nil {
		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		context.Abort()
		return
	}
	if err := user.HashPassword(user.Password); err != nil {
		context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		context.Abort()
		return
	}
	record := database.Instance.Create(&user)
	if record.Error != nil {
		context.JSON(http.StatusInternalServerError, gin.H{"error": record.Error.Error()})
		context.Abort()
		return
	}
	context.JSON(http.StatusCreated, gin.H{"userId": user.ID, "email": user.Email, "username": user.Username})
}

Register() method ShouldBindJson() would get the user input and map into user object of models.User. It's all about converting json to model object.

Then we call HashPassword() which we created before for password encrypt. 

If Hashing is successful, we insert user info to the database and return a record of the inserted row.

Token Controller

This would be most exciting part of the tutorial how to generate JWT token. We would also add some helper functions. Helper functions would be in auth folder. Let's create a folder inside the root folder and create a file name auth.go

package auth
import (
	"errors"
	"time"
	"github.com/dgrijalva/jwt-go"
)
var jwtKey = []byte("supersecretkey")
type JWTClaim struct {
	Username string `json:"username"`
	Email    string `json:"email"`
	jwt.StandardClaims
}
func GenerateJWT(email string, username string) (tokenString string, err error) {
	expirationTime := time.Now().Add(1 * time.Hour)
	claims:= &JWTClaim{
		Email: email,
		Username: username,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expirationTime.Unix(),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err = token.SignedString(jwtKey)
	return
}
func ValidateToken(signedToken string) (err error) {
	token, err := jwt.ParseWithClaims(
		signedToken,
		&JWTClaim{},
		func(token *jwt.Token) (interface{}, error) {
			return []byte(jwtKey), nil
		},
	)
	if err != nil {
		return
	}
	claims, ok := token.Claims.(*JWTClaim)
	if !ok {
		err = errors.New("couldn't parse claims")
		return
	}
	if claims.ExpiresAt < time.Now().Local().Unix() {
		err = errors.New("token expired")
		return
	}
	return
}

At the beginning we have a secret key, for now we have a dummy key. In production, you should use your real key.

Later, we have created a struct, this struct would be the payload of JWT. This could be found in generated token. We will see about it later.

After that, we generate the string using GenerateJWT() method. It has expiration time, user information. This method would get called when you try to login and along the way, we will generate tokent.

After that, we just claim it, meaning that this token is ours. It also assigns a special signature. 

Then we created a function ValidateToken(), to receive the incoming token from the headers and validate it. This method would get called when we try to access a protected route. We will see this protected routes later.

Front end or api call would send a token and ValidateToken() method would take it and analyze it. 

Create another file name tokencontroller.go inside controllers folder and add the code below

package controllers
import (
	"jwt-authentication-golang/auth"
	"jwt-authentication-golang/database"
	"jwt-authentication-golang/models"
	"net/http"
	"github.com/gin-gonic/gin"
)
type TokenRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}
func GenerateToken(context *gin.Context) {
	var request TokenRequest
	var user models.User
	if err := context.ShouldBindJSON(&request); err != nil {
		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		context.Abort()
		return
	}
	// check if email exists and password is correct
	record := database.Instance.Where("email = ?", request.Email).First(&user)
	if record.Error != nil {
		context.JSON(http.StatusInternalServerError, gin.H{"error": record.Error.Error()})
		context.Abort()
		return
	}
	credentialError := user.CheckPassword(request.Password)
	if credentialError != nil {
		context.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		context.Abort()
		return
	}
	tokenString, err:= auth.GenerateJWT(user.Email, user.Username)
	if err != nil {
		context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		context.Abort()
		return
	}
	context.JSON(http.StatusOK, gin.H{"token": tokenString})
}

Here, we see the we called auth.GenerateJWT(). So the steps are

ShouldBindJson->Find User->CheckPassord->GenerateJWT

The above process is very important and ususally used throughout programming paradimg for generating token.

Secured Controller

It's time to create the last controller. After that we will move to testing every thing we have done so far. This controller will be protected by middleware. Of course we did not create the middleware yet.

let's create a new file securedcontroller.go inside the controllers folder. You may put the below code 

package controllers
import (
	"net/http"
	"github.com/gin-gonic/gin"
)
func Secured(context *gin.Context) {
	context.JSON(http.StatusOK, gin.H{"message": "We are connected now. You passed the security"})
}

Inside the file we have a function name Secured. It's our end point. This would be accessed if we go through middleware. Let's create our middleware then

Middleware

Middleware is a middlemen between different http requests. When you call a http request you want them to have some restrictions. Some end points are open to the public and some are not.

In our case login and resigterUser functions are public and Secured() function is not.

Create a folder in the root folder and name it auth and inside it create auth.go file and put the code below

package middlewares

import (
	"github.com/gin-gonic/gin"

	"jwt-authentication-golang/auth"
)

func Auth() gin.HandlerFunc {
	return func(context *gin.Context) {
		tokenString := context.GetHeader("Authorization")
		if tokenString == "" {
			context.JSON(401, gin.H{"error": "request does not contain an access token"})
			context.Abort()
			return
		}
		err := auth.ValidateToken(tokenString)
		if err != nil {
			context.JSON(401, gin.H{"error": err.Error()})
			context.Abort()
			return
		}
		context.Next()
	}
}

Here Auth() function is our middleware, and it checks for header or token and then also check if this token is expired or not.

If this is not expired, it allows to excuse the desired http request.

Entry point

Now in our main.go file we need to change the code. We will have routes and group routes. 

package main

import (
	"github.com/gin-gonic/gin"

	"jwt-authentication-golang/controllers"
	"jwt-authentication-golang/database"
	"jwt-authentication-golang/middlewares"
)

func main() {
	// Initialize Database
	database.Connect("root:123456@tcp(localhost:3306)/jwt_projects?parseTime=true")
	database.Migrate()
	// Initialize Router
	router := initRouter()
	router.Run(":8080")
}
func initRouter() *gin.Engine {
	router := gin.Default()
	api := router.Group("/api")
	{
		api.POST("/token", controllers.GenerateToken)
		api.POST("/user/register", controllers.RegisterUser)
		secured := api.Group("/secured").Use(middlewares.Auth())
		{
			secured.GET("/ping", controllers.Ping)
		}
	}
	return router
}

Here you see main() function creates our database connection, then migrates and after that it initializes routes. Public routes are accessed with /api/routeName and protected routes first go through Auth() function. Here Use() function is available from Gin framework.

Comment

Add Reviews