JWT Authentication in Golang using Gin Web Framework-Tutorial

JWT Authentication using Golang
's picture

In this tutorial, I will create a JWT authentication Application in Golang based on Gin Web Framework. I will be using the JWT package to create the entire authentication logic. I will be using Gin Framework to create our API, therefore, the application will contain API for Signup, login, getting all the users using tokens, and getting the user by its ID. This blog will also guide you on, what is JWT - Authentication, when to use JWT-Authentication, how to make such an application, and errors that I faced during the whole process of coding that you should avoid. If you want to learn go web development, you have landed at the right place. Before I proceed, I am mentioning the prerequisites so that you can be prepared with them before proceeding with this blog for a better understanding.

Prerequisites

To begin with, you should know how to Set-Up the directory for your project.
You should know about Mod Files, Packages, and Functions
You should have knowledge regarding concepts like Struct, Slices, Routing, and Database Connection.
For Database Connection, I will be using MongoDB Atlas as my database. Therefore, you should be aware of Clusters and how to fetch your Connection String from MongoDB Atlas and integrate it into your code.
In Bonus, you should know about API and Gin Web Framework
And Finally the installation part i.e., you need to install Postman Application (To check the APIs or Test your Golang Application), Go, and IDE i.e., Visual Studio Code for this project.

JWT Authentication

Let us understand what is exactly JWT Authentication. JWT stands for JSON web token and it is a token-based stateless authentication method. It is commonly utilized as a client-side-based stateless session, allowing the server to save session data without totally relying on a database.JWT is used particularly in server-to-server authorization and API authentication.

Creating the Application

The first step includes the creation of a directory for our project through the console and opening up the IDE for writing our code. For this blog, I will be using VS Code Editor as mentioned in the prerequisites. I will be mentioning the folders that you need to create. I will also mention the external packages that you need to import for this project while explaining the code but first let's begin with the folder that you need to create for your project. The purpose of those folders is also explained for your ease of understanding, especially when working with a "golang development company."

Folders for the project

First I will begin with my main.go file in the main directory and then proceed to other folders i.e., routes, models, middleware, helpers, and controllers. You can refer to Image 1 for the files that you need to create inside these folders.

main.go file in the main directory
[Image 1]

The above Image gives an idea about the structure of the code. The reasons behind this kind of structure have been explained in coding sections.

Working on the main.go

As known, the initial file that we are going to create is “main.go”. In “main.go”, I will include some packages like, “Gin” package and start with our main function. In my main.go file, I have also imported routes i.e “github.com/golangcompany/JWT-Authentication/routes”.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package main import ( "github.com/gin-gonic/gin" routes "github.com/golangcompany/JWT-Authentication/routes" ) func main() { port := "8000" router := gin.New() routes.UserRoutes(router) routes.AuthRoutes(router) router.GET("/api-1", func(c *gin.Context) { c.JSON(200, gin.H{"success": "Access granted for api-1"}) }) router.GET("/api-2", func(c *gin.Context) { c.JSON(200, gin.H{"success": "Access granted for api-2"}) }) router.Run(":" + port) }

Here, in “func main” I have defined a variable “port” with a value of 8000 and a router with “gin.New()”. In router gin.New, New returns a new blank Engine instance without any middleware attached. You could also add a “router.Use(gin.Logger())” in order to log the process. Then, you need to mention to functions i.e., “UserRoutes” and “AuthRoutes” that will be defined in the routes package and passes the router variable in it. This is a short approach that I took. Instead of this approach, I could have included a port variable that asks to get the port number from the environment variable and added an “if” condition which would have stated that if no port is set then it should establish a port (8000). You can take both approaches but taking the .env file into account is a good practice. If I would have taken the second approach, then two other packages were required to import as well i.e., “os” and “github.com/joho/godotenv”.

Working on Struct

In my second step, I created a struct in userModels.go file that contains fields like ID, First Name, Last Name, Password, E-Mail, Phone Number, Token ID, User Type, Refresh Token ID, Created at, Updated at, and User ID. I have also imported the “primitive” package since I want the id to be different every time whenever I post user data in our database. In the code as you can see, I have used validate, It's used to check if the data entered from the client side is according to the format we have programmed for, and if not it returns an error. For fields like “Created at” and “Updated at”, I have used the “time.Time” data type since we need the timestamp for it and also imported the time package for it to work.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package models import ( "go.mongodb.org/mongo-driver/bson/primitive" "time" ) type User struct { ID primitive.ObjectID `bson:"_id"` First_name *string `json:"first_name" validate:"required,min=2,max=100"` Last_name *string `json:"last_name" validate:"required,min=2,max=100"` Password *string `json:"Password" validate:"required,min=6"` Email *string `json:"email" validate:"email,required"` Phone *string `json:"phone" validate:"required"` Token *string `json:"token"` User_type *string `json:"user_type" validate:"required,eq=ADMIN|eq=USER"` Refresh_token *string `json:"refresh_token"` Created_at time.Time `json:"created_at"` Updated_at time.Time `json:"updated_at"` User_id string `json:"user_id"` }

I have also mentioned eq=ADMIN|eq=USER in the User_type field since It validates the value of the field user_type to be either ADMIN or USER.

Routes

Now comes the route, Here we will create a folder routes.go and define the the two functions that we have mentioned in the main.go file. first, let us begin UserRoutes function that I have mentioned in my “main.go” file. Here, I have first defined the UserRoutes function that has a parameter “incomingRoutes”. Basically, this function has two POST methods, one is for Signup and another one is for Login. In both POST methods, I have mentioned two arguments i.e., a string and a function which instructs the path of the route ("users/signup" and "users/login"). This means that when a client makes an HTTP request to server with the path “users/signup” or “users/login”, then Signup or Login function will be called which is defined in controller package.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package routes import ( "github.com/gin-gonic/gin" controller "github.com/golangcompany/JWT-Authentication/controllers" "github.com/golangcompany/JWT-Authentication/middleware" ) func UserRoutes(incomingRoutes *gin.Engine) { incomingRoutes.POST("users/signup", controller.Signup()) incomingRoutes.POST("users/login", controller.Login()) } func AuthRoutes(incomingRoutes *gin.Engine) { incomingRoutes.Use(middleware.UserAuthenticate()) incomingRoutes.GET("/usersdata", controller.GetUsers()) incomingRoutes.GET("/users/:user_id", controller.GetUser()) }

For defining AuthRoutes function we have used the GET me

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package database import ( "context" "fmt" "log" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func DBSet() *mongo.Client { client, err := mongo.NewClient(options.Client().ApplyURI("mongodb+srv://rishav_4waytechnologies:<Password>@cluster0.0aeeqhe.mongodb.net/?retryWrites=true&w=majority")) if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel()

thod but mentioned USE method with argument function UserAuthenticate defined in middleware.
The reason I am using “middleware.Authenticate” here is to ensure that both these routes are protected routes. I haven’t used middleware while defining my “UserRoutes” function since the user does not have the token at that point in time. We will include it all in a function for AuthRoutes that routes incoming requests to the correct controller.

Connecting Database (MongoDB)

Here, I will create a function named as DBSet. The function connects to the MongoDB database and returns the client object if successful, otherwise it will return nil. In my code, I have mentioned the URI in the client variable. To fetch a connection string should be aware, how to connect with MongoDB Cluster. Next, I declared a variable named Client of type *mongo.Client. The value of the variable is set to the return value of the function DBSet(). And at the end, I defined a function named UserData that returns a pointer to a mongo collection. It takes in two parameters i.e., A pointer to the client, which is used to connect to the database, and the name of the collection you want to access. The code for the databaseConnection.go file is below for your reference,

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package database import ( "context" "fmt" "log" "time" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) func DBSet() *mongo.Client { client, err := mongo.NewClient(options.Client().ApplyURI("mongodb+srv://rishav_4waytechnologies:<Password>@cluster0.0aeeqhe.mongodb.net/?retryWrites=true&w=majority")) if err != nil { log.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = client.Connect(ctx) if err != nil { log.Fatal(err) } err = client.Ping(context.TODO(), nil) if err != nil { log.Println("Failed to Connect") return nil } fmt.Println("Successfully Connected to the Mongodb") return client } var Client *mongo.Client = DBSet() func UserData(client *mongo.Client, CollectionName string) *mongo.Collection { var collection *mongo.Collection = client.Database("Cluster0").Collection(CollectionName) return collection }

After the establishment of a connection with my database I will move ahead with the userControllers.go file now.

Establishing Controllers

Starting with importing some internal packages like, "context", "fmt", "log", "net/http", "strconv", and "time". Here the strconv package in the Go programming language is used to convert from one datatype to another.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package controllers import ( "context" "fmt" "log" "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "github.com/golangcompany/JWT-Authentication/database" helper "github.com/golangcompany/JWT-Authentication/helpers" "github.com/golangcompany/JWT-Authentication/models" "golang.org/x/crypto/bcrypt" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" )

Then I declared a variable called userCollection and assign it the value of database.UserData(database.Client, "user"). The type of this variable is *mongo.Collection.

1 2 var userCollection *mongo.Collection = database.UserData(database.Client, "user") var validate = validator.New()

Moving ahead, I will be creating the functions for my controller like HashPassword, VerifyPassword, Signup, Login, and two GetUser functions since we want to fetch all users and users by ID. Hashpassword is a function that hashes the password. The function uses bcrypt (Hashing Algorithm) to hash the password. Here, Hashing Password means converting the password of plaintext into cryptic letters and numbers. For this scenario, I have also imported the bcrypt package.

1 2 3 4 5 6 7 func HashPassword(password string) string { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) if err != nil { log.Panic(err) } return string(bytes) }

Next, I have defined VerifyPassword Function, It is a function that verifies the password. It takes two arguments: userPassword and providedPassword. The function returns a boolean value and a string value. CompareHashAndPassword compares a bcrypt hashed password with its possible plaintext equivalent. Returns nil on success, or an error on failure which then gives an error message “E-mail or Password is incorrect”.

1 2 3 4 5 6 7 8 9 10 11 func VerifyPassword(userPassword string, providedPassword string) (bool, string) { err := bcrypt.CompareHashAndPassword([]byte(providedPassword), []byte(userPassword)) check := true msg := "" if err != nil { msg = fmt.Sprintf("E-Mail or Password is incorrect") check = false } return check, msg }

After the VerifyPassword function, I will define the Signup function which is a function that creates a new user. It takes in the user's details and saves them to the database. The function begins by creating a context with a timeout using the context.WithTimeout function and assigning it to the ctx variable. A cancel function is also returned, which can be used to cancel the context when it is no longer needed. The function then uses the validate package to validate the user struct using the Struct function. then counts the number of documents in the userCollection that have the same email as the user using the CountDocuments method. If there are no errors, then it hashes the password and generates tokens for authentication. Finally, it inserts all of this information into the MongoDB database.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 func Signup() gin.HandlerFunc { return func(c *gin.Context) { var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second) var user models.User if err := c.BindJSON(&user); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } validationErr := validate.Struct(user) if validationErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": validationErr.Error()}) return } count, err := userCollection.CountDocuments(ctx, bson.M{"email": user.Email}) defer cancel() if err != nil { log.Panic(err) c.JSON(http.StatusInternalServerError, gin.H{"error": "error detected while fetching the email"}) } password := HashPassword(*user.Password) user.Password = &password count, err = userCollection.CountDocuments(ctx, bson.M{"phone": user.Phone}) defer cancel() if err != nil { log.Panic(err) c.JSON(http.StatusInternalServerError, gin.H{"Error": "Error occured while fetching the phone number"}) } if count > 0 { c.JSON(http.StatusInternalServerError, gin.H{"Error": "The mentioned E-Mail or Phone Number already exists"}) } user.Created_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) user.Updated_at, _ = time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) user.ID = primitive.NewObjectID() user.User_id = user.ID.Hex() token, refreshToken, _ := helper.GenerateAllTokens(*user.Email, *user.First_name, *user.Last_name, *user.User_type, *&user.User_id) user.Token = &token user.Refresh_token = &refreshToken resultInsertionNumber, insertErr := userCollection.InsertOne(ctx, user) if insertErr != nil { msg := fmt.Sprintf("User Details were not Saved") c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) return } defer cancel() c.JSON(http.StatusOK, resultInsertionNumber) } }

Next, I mentioned the Login function, which is a function that handles the login of users. It takes in an email and password, checks if they exist in the database, and returns a token to the user.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func Login() gin.HandlerFunc { return func(c *gin.Context) { var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second) var user models.User var foundUser models.User if err := c.BindJSON(&user); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } err := userCollection.FindOne(ctx, bson.M{"email": user.Email}).Decode(&foundUser) defer cancel() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "email or password is incorrect"}) return } passwordIsValid, msg := VerifyPassword(*user.Password, *foundUser.Password) defer cancel() if passwordIsValid != true { c.JSON(http.StatusInternalServerError, gin.H{"error": msg}) return } if foundUser.Email == nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "user not found"}) } token, refreshToken, _ := helper.GenerateAllTokens(*foundUser.Email, *foundUser.First_name, *foundUser.Last_name, *foundUser.User_type, foundUser.User_id) helper.UpdateAllTokens(token, refreshToken, foundUser.User_id) err = userCollection.FindOne(ctx, bson.M{"user_id": foundUser.User_id}).Decode(&foundUser) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, foundUser) } }

In the final steps for establishing controllers, I have defined the GetUsers function which will first check the user type to make sure it is "ADMIN" then a context is mentioned with a timeout of 100 seconds. then creates three stages for an Aggregate funnel using the MongoDB Go package: a match stage, a group stage, and a project stage. It uses these stages to retrieve data from the "userCollection". Lastly, the GetUser function defines to get the user information from the database by its user ID.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 func GetUsers() gin.HandlerFunc { return func(c *gin.Context) { if err := helper.CheckUserType(c, "ADMIN"); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second) recordPerPage, err := strconv.Atoi(c.Query("recordPerPage")) if err != nil || recordPerPage < 1 { recordPerPage = 10 } page, err1 := strconv.Atoi(c.Query("page")) if err1 != nil || page < 1 { page = 1 } startIndex := (page - 1) * recordPerPage startIndex, err = strconv.Atoi(c.Query("startIndex")) matchStage := bson.D{{Key: "$match", Value: bson.D{{}}}} groupStage := bson.D{{Key: "$group", Value: bson.D{ {Key: "_id", Value: bson.D{{Key: "_id", Value: "null"}}}, {Key: "total_count", Value: bson.D{{Key: "$sum", Value: 1}}}, {Key: "data", Value: bson.D{{Key: "$push", Value: "$$ROOT"}}}}}} projectStage := bson.D{ {Key: "$project", Value: bson.D{ {Key: "_id", Value: 0}, {Key: "total_count", Value: 1}, {Key: "user_items", Value: bson.D{{Key: "$slice", Value: []interface{}{"$data", startIndex, recordPerPage}}}}}}} result, err := userCollection.Aggregate(ctx, mongo.Pipeline{ matchStage, groupStage, projectStage}) defer cancel() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "error occured while listing user items"}) } var allusers []bson.M if err = result.All(ctx, &allusers); err != nil { log.Fatal(err) } c.JSON(http.StatusOK, allusers[0]) } } func GetUser() gin.HandlerFunc { return func(c *gin.Context) { userId := c.Param("user_id") if err := helper.MatchUserTypeToUid(c, userId); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second) var user models.User err := userCollection.FindOne(ctx, bson.M{"user_id": userId}).Decode(&user) defer cancel() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, user) } }

Why Middleware?

Authentication function checks if the client has provided a token in the request header. If not, it returns an error message and aborts the request. If the user has a valid token it sets some values in the context (c) and calls c.Next() to continue with the next handler. If the user does not have a valid token it returns an error message and aborts the request. Here, “c.Set()” is a method that takes two arguments: the key and the value. The key is a string and the value can be any type of data. And we are calling c.Next at the end of the function Because we want to continue with the next handler in line. If we don't call it, the request will stop here and no other handlers will be called. The below code will give you more clarity on the above explanation,

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package middleware import ( "fmt" "github.com/gin-gonic/gin" helper "github.com/golangcompany/JWT-Authentication/helpers" "net/http" ) func Authenticate() gin.HandlerFunc { return func(c *gin.Context) { clientToken := c.Request.Header.Get("token") if clientToken == "" { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("No Authorization Header Provided")}) c.Abort() return } claims, err := helper.ValidateToken(clientToken) if err != "" { c.JSON(http.StatusInternalServerError, gin.H{"error": err}) c.Abort() return } c.Set("email", claims.Email) c.Set("first_name", claims.First_name) c.Set("last_name", claims.Last_name) c.Set("uid", claims.Uid) c.Set("user_type", claims.User_type) c.Next() } }

Defining Helpers

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package helper import ( "errors" "github.com/gin-gonic/gin" ) func CheckUserType(c *gin.Context, role string) (err error) { userType := c.GetString("user_type") err = nil if userType != role { err = errors.New("You are not authorised to access this resource") return err } return err } func MatchUserTypeToUid(c *gin.Context, userId string) (err error) { userType := c.GetString("user_type") uid := c.GetString("uid") err = nil if userType == "USER" && uid != userId { err = errors.New("You are not authorised to access this resource") return err } err = CheckUserType(c, userType) return err }

The above code is for authHelper.go which contains two functions. The first function, CheckUserType, checks if the user type in the context matches the role passed to it as an argument. If they don't match, it returns an error. The second function, MatchUserTypeToUid, checks if the user type in the context matches "USER" and if so, it checks if the uid in the context matches the userId passed to it as an argument. If either of these conditions are not met, then this function returns an error.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 package helper import ( "context" "fmt" jwt "github.com/dgrijalva/jwt-go" "github.com/golangcompany/JWT-Authentication/database" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "log" "os" "time" ) type SignedDetails struct { Email string First_name string Last_name string Uid string User_type string jwt.StandardClaims } var userCollection *mongo.Collection = database.UserData(database.Client, "user") var SECRET_KEY string = os.Getenv("SECRET_KEY") func GenerateAllTokens(email string, firstName string, lastName string, userType string, uid string) (signedToken string, signedRefreshToken string, err error) { claims := &SignedDetails{ Email: email, First_name: firstName, Last_name: lastName, Uid: uid, User_type: userType, StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Local().Add(time.Hour * time.Duration(24)).Unix(), }, } refreshClaims := &SignedDetails{ StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Local().Add(time.Hour * time.Duration(168)).Unix(), }, } token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(SECRET_KEY)) refreshToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString([]byte(SECRET_KEY)) if err != nil { log.Panic(err) return } return token, refreshToken, err } func ValidateToken(signedToken string) (claims *SignedDetails, msg string) { token, err := jwt.ParseWithClaims( signedToken, &SignedDetails{}, func(token *jwt.Token) (interface{}, error) { return []byte(SECRET_KEY), nil }, ) if err != nil { msg = err.Error() return } claims, ok := token.Claims.(*SignedDetails) if !ok { msg = fmt.Sprintf("the token is invalid") msg = err.Error() return } if claims.ExpiresAt < time.Now().Local().Unix() { msg = fmt.Sprintf("token is expired") msg = err.Error() return } return claims, msg } func UpdateAllTokens(signedToken string, signedRefreshToken string, userId string) { var ctx, cancel = context.WithTimeout(context.Background(), 100*time.Second) var updateObj primitive.D updateObj = append(updateObj, bson.E{Key: "token", Value: signedToken}) updateObj = append(updateObj, bson.E{Key: "refresh_token", Value: signedRefreshToken}) Updated_at, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339)) updateObj = append(updateObj, bson.E{Key: "updated_at", Value: Updated_at}) upsert := true filter := bson.M{"user_id": userId} opt := options.UpdateOptions{ Upsert: &upsert, } _, err := userCollection.UpdateOne( ctx, filter, bson.D{ {Key: "$set", Value: updateObj}, }, &opt, ) defer cancel() if err != nil { log.Panic(err) return } return }

The tokenHelper.go file generates and validates JWT tokens. It also updates the user's token in the database. In this, I have included three functions i.e., GenerateAllTokens, ValidateToken, and UpdateAllTokens. The GenerateAllTokens function generates a token and a refresh token. The token expires in 24 hours, while the refresh token expires in 7 days.
The ValidateToken function validates the token. It returns the claims if it's valid and an error message if they are not. Finally, The UpdateAllTokens function updates the user's token in the database.

Checking the API

Now, it’s time to run the program by the command “go run main.go”. Which gives us output in our terminal (Refer to Image - 2)

output in our terminal
[Image 2]

Here, you can see that my program is running fine and now, let's check whether the API is working properly or not. For that, I will open the postman application, create a new collection, and in that collection, I add a new POST Request. Then type the URL i.e., “http://localhost:8000/users/signup”. I am creating this request to check our Signup API.
After that, I will select the body and enter fields like First Name, Last Name, Password, Email, Phone, and Usertype (Admin/User), Finally, after clicking on send, it gives us a message that “Inserted ID”. This means that our Signup is working perfectly fine.

POST Request
[Image 3]

Now, let us check the database as well to confirm whether our database is working fine or not.

database
[Image 4]

From Image - 4, you can see that the data is in our database. Therefore the database connection is also working absolutely fine.
I have attached other images for your reference where I have checked the other three APIs (Login, FindAllUser, and FindUserByID) for your reference (Refer to Images 5, 6, 7 & 8). Hope this blog will be helpful for your learning.

 checked the other three APIs
[Image 5]
[Image 6]
[Image 6]
[Image 7]
[Image 7]
[Image 8]
[Image 8]

Build Your Golang Team