JWT Authentication in Golang using Gin Web Framework-Tutorial

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 golang in 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.
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.

[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)

[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.

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

[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.

[Image 5]
![[Image 6]](https://cdn.sanity.io/images/nlvljy00/production/37a32b50e7483cdba2a7bd2f1f7badc53740a34b-512x288.png?fit=max&auto=format)
[Image 6]
![[Image 7]](https://cdn.sanity.io/images/nlvljy00/production/2a1a37b71636b9f1afb587a7dce56d43a631b8fe-512x288.png?fit=max&auto=format)
[Image 7]
![[Image 8]](https://cdn.sanity.io/images/nlvljy00/production/8f43d82bad45100e767368ee83570c7fe97c4bfd-512x288.png?fit=max&auto=format)
[Image 8]

Rishav’s professional life began in 2019 when he completed his B. Tech in computer science and engineering. He has been in content development for the last 2.5 years. Tech is not his profession, it’s his curiosity that he believes to explore as much as he can.