Build user authentication in Golang with JWT and mongoDB
Tags: jwt, go, mongodb, auth
Published: 2021-02-03 12:27:49

>*This article assumes that you already have Golang installed and you are aware of how to setup a simple golang app. If not don't worry the article will teach you how to do that.
This article also assumes that you have a working mongodb database available for this tutorial if not refer to the first section of this [article](https://dev.to/joojodontoh/build-an-api-endpoint-with-golang-gin-2065) to get one*  

Let's say you wanted to build a secure Restful API only available to your user base or registered users, how would you achieve that? 🤔 This article is going to teach you just how to do that in Golang. The idea is mostly the same in other languages so once you grasp the concept it shouldn't  be too hard to implement anywhere. 


###General concept
1. First the user logs in from a client device which makes an API call to the server with the user's details
2. The server verifies these details from the user to make sure the user exists in the system
3. Once verification is complete, the server sends a secure token to client device.
4. The received token can now be used by the client to grant access to api resources for the user.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/95i70w5nmik4oieorris.jpg)


###Development Overview
1. Setup.
 * Application setup
 * Database setup
2. Create a User module.
 * User model
3. Install and implement the [bcrypt](https://pkg.go.dev/golang.org/x/crypto/bcrypt) package.
 * Password hashing
 * Password verification upon login
 * Sign up service
 * Login Service
4. Install and implement the [JWT-GO](https://pkg.go.dev/github.com/dgrijalva/jwt-go) package.
 * build token generator
 * build token validator
 * update token upon login
5. Implement JWT middleware for validation.
6. Setup routes.
7. Write main.go file.


##Let's begin!
This is the file structure you'll be using 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yxoor7c5hll7hdqcqaxm.png)



####Setting up the application
```
$ go build
$ ./new
```
`go build` compiles the application. `./new` is used to run the compiled file. This file will be located in your root folder and may have another name. I simply named mine new. 

* Create a folder and name it whatever you please. I'm gonna name mine user-athentication-golang. This is going to be your root folder.

* My prefered IDE is VS code. Open the terminal in VScode and run `go mod init`. This will automatically create your go.mod file (a file that stores all the required packages for your app) and save it in your root folder.

* Create a .env file (a file for storing your environmental variables) and fill it with the following.
```
PORT=8000
MONGODB_URL={{insert the db link here}}
```

* Create a folder (package) named "database". This folder will contain a file which manages your database connection and opens your collections. Create the file and name "databaseConnection.go" and fill it with the following code.
```
package database

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/joho/godotenv"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

//DBinstance func
func DBinstance() *mongo.Client {
	err := godotenv.Load(".env")

	if err != nil {
		log.Fatal("Error loading .env file")
	}

	MongoDb := os.Getenv("MONGODB_URL")

	client, err := mongo.NewClient(options.Client().ApplyURI(MongoDb))
	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)
	}
	fmt.Println("Connected to MongoDB!")

	return client
}

//Client Database instance
var Client *mongo.Client = DBinstance()

//OpenCollection is a  function makes a connection with a collection in the database
func OpenCollection(client *mongo.Client, collectionName string) *mongo.Collection {

	var collection *mongo.Collection = client.Database("cluster0").Collection(collectionName)

	return collection
}

```

* Create a Main.go file in your root folder and leave it empty for now




You are going to start by creating the user model. The user model is basically the structure of user details for every user in your system. Every user must at least have a 
* user ID (a unique string that identifies users)
* first name 
* last name
* email
* password
* token (the signed jwt token with the user details)
* refresh token (an empty token for simply refreshing a page)

Follow the file structure as shown above and insert the following code into your `models/userModel.go` file
```
package models

import (
	"time"

	"go.mongodb.org/mongo-driver/bson/primitive"
)

//User is the model that governs all notes objects retrived or inserted into the DB
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"`
	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"`
}

```
### Bcrypt and password management
Now let's move on to getting the [bcrypt](https://pkg.go.dev/golang.org/x/crypto/bcrypt) package, [validator](https://github.com/go-playground/validator) package and the [gin](https://pkg.go.dev/github.com/gin-gonic/gin) package which will be used for password management, struct validation and route management respectively.
In your `controllers/userController.go` file, insert the following code
```
package controllers

import (
	"context"
	"fmt"
	"log"

	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"

	"user-athentication-golang/database"

	helper "user-athentication-golang/helpers"
	"user-athentication-golang/models"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"golang.org/x/crypto/bcrypt"
)

var userCollection *mongo.Collection = database.OpenCollection(database.Client, "user")
var validate = validator.New()
```
Now implement password hashing and verification in your `controllers/userController.go` file with the following code.
```
//HashPassword is used to encrypt the password before it is stored in the DB
func HashPassword(password string) string {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
	if err != nil {
		log.Panic(err)
	}

	return string(bytes)
}

//VerifyPassword checks the input password while verifying it with the passward in the DB.
func VerifyPassword(userPassword string, providedPassword string) (bool, string) {
	err := bcrypt.CompareHashAndPassword([]byte(providedPassword), []byte(userPassword))
	check := true
	msg := ""

	if err != nil {
		msg = fmt.Sprintf("login or passowrd is incorrect")
		check = false
	}

	return check, msg
}
``` 
Now let's move on to implementing the signup and login service in your `controllers/userController.go` file with the following code. Kindly note that some functions such as `helper.GenerateAllTokens` and `helper.UpdateAllTokens` will not be available since you haven't written them yet. keep calm 😁😁
```
//CreateUser is the api used to tget a single user
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 occured while checking for the email"})
			return
		}

		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 checking for the phone number"})
			return
		}

		if count > 0 {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "this email or phone number already exists"})
			return
		}

		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_id)
		user.Token = &token
		user.Refresh_token = &refreshToken

		resultInsertionNumber, insertErr := userCollection.InsertOne(ctx, user)
		if insertErr != nil {
			msg := fmt.Sprintf("User item was not created")
			c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
			return
		}
		defer cancel()

		c.JSON(http.StatusOK, resultInsertionNumber)

	}
}

//Login is the api used to tget a single user
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": "login or passowrd is incorrect"})
			return
		}

		passwordIsValid, msg := VerifyPassword(*user.Password, *foundUser.Password)
		defer cancel()
		if passwordIsValid != true {
			c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
			return
		}

		token, refreshToken, _ := helper.GenerateAllTokens(*foundUser.Email, *foundUser.First_name, *foundUser.Last_name, foundUser.User_id)

		helper.UpdateAllTokens(token, refreshToken, foundUser.User_id)

		c.JSON(http.StatusOK, foundUser)

	}
}
```

### Token management
Now let's move on to implementing the JWT/token functionality. Get and install the [JWT-GO](https://pkg.go.dev/github.com/dgrijalva/jwt-go) package the app. Reference all the needed packages in the `helpers/tokenHelper.go` file with the import block below.
```
package helper

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"user-athentication-golang/database"

	jwt "github.com/dgrijalva/jwt-go"
	"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"
)
```

Now add a [struct](https://gobyexample.com/structs) for all the details you'll be signing into the token. Append the following code to the `helpers.tokenHelper.go` file.
```
// SignedDetails
type SignedDetails struct {
	Email      string
	First_name string
	Last_name  string
	Uid        string
	jwt.StandardClaims
}
```
Once that is done, implement the token generation function with the following code. Append it to the `helpers.tokenHelper.go` file.
```
var userCollection *mongo.Collection = database.OpenCollection(database.Client, "user")

var SECRET_KEY string = os.Getenv("SECRET_KEY")

// GenerateAllTokens generates both teh detailed token and refresh token
func GenerateAllTokens(email string, firstName string, lastName string, uid string) (signedToken string, signedRefreshToken string, err error) {
	claims := &SignedDetails{
		Email:      email,
		First_name: firstName,
		Last_name:  lastName,
		Uid:        uid,
		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
}
```
Implement the token validation function in the `helpers.tokenHelper.go` file with the code below.
```
//ValidateToken validates the jwt token
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
}
```
When a user logs in, you'll need to reassign them with new tokens that expire at a later time. This simply means that you'll be updating the user's tokens in the DB whenever they login. Implement this feature in the `helpers.tokenHelper.go` file with the code below.
```
//UpdateAllTokens renews the user tokens when they login
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{"token", signedToken})
	updateObj = append(updateObj, bson.E{"refresh_token", signedRefreshToken})

	Updated_at, _ := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
	updateObj = append(updateObj, bson.E{"updated_at", Updated_at})

	upsert := true
	filter := bson.M{"user_id": userId}
	opt := options.UpdateOptions{
		Upsert: &upsert,
	}

	_, err := userCollection.UpdateOne(
		ctx,
		filter,
		bson.D{
			{"$set", updateObj},
		},
		&opt,
	)
	defer cancel()

	if err != nil {
		log.Panic(err)
		return
	}

	return
}
``` 

### Middleware control

Now that you have all the functionality for the tokens ready, you can fit them into the middleware that does the checking before API calls get in the routes. To do this, insert the following code into the `middleware/authMiddleware.go` file
```
package middleware

import (
	"fmt"
	"net/http"

	helper "user-athentication-golang/helpers"

	"github.com/gin-gonic/gin"
)

// Authz validates token and authorizes users
func Authentication() 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.Next()

	}
}

``` 
### Setup routes
In the `routes/userRoutes.go` file, insert the following code to make out signup and login APIs available to the `main.go` file.

```
package routes

import (
	controller "user-athentication-golang/controllers"

	"github.com/gin-gonic/gin"
)

//UserRoutes function
func UserRoutes(incomingRoutes *gin.Engine) {
	incomingRoutes.POST("/users/signup", controller.SignUp())
	incomingRoutes.POST("/users/login", controller.Login())
}

```

### Main.go file
Now you can round it all off with your main.go file by inserting the following code. The following code includes 2 dummy APIs which will be used to demonstrate the token's validity.
The middleware is used only after the user route because there is no need for token validation during login and signup 
```
package main

import (
	"os"

	middleware "user-athentication-golang/middleware"
	routes "user-athentication-golang/routes"

	"github.com/gin-gonic/gin"
	_ "github.com/heroku/x/hmetrics/onload"
)

func main() {
	port := os.Getenv("PORT")

	if port == "" {
		port = "8000"
	}

	router := gin.New()
	router.Use(gin.Logger())
	routes.UserRoutes(router)

	router.Use(middleware.Authentication())

	// API-2
	router.GET("/api-1", func(c *gin.Context) {

		c.JSON(200, gin.H{"success": "Access granted for api-1"})

	})

	// API-1
	router.GET("/api-2", func(c *gin.Context) {
		c.JSON(200, gin.H{"success": "Access granted for api-2"})
	})

	router.Run(":" + port)
}
```

### Run and test
To run the app, first build it by running this command `go build -o new -v` in the terminal and then run `go run main.go` to allow the application listen for API requests. Your app should be available on `http://localhost:8000` and your terminal should look something like this: 


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hmqxtrl368xspe05yhjp.png)

####Postman
Insert the URL: `http://localhost:8000` into postman and append the relevant extensions and request body if needed. Examples can be seen below.


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/spq3uzzszf6ifywfcbhp.png)![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3eq5pljokm985m4rgy2d.png)![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qi9f2rpdt9l6bso2gjvu.png)![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ygsnf9pwb0d8t3r7734g.png)


## Conclusion
In this article, you learnt how to setup a simple golang application with a working mongoDB database. You also created a user module while implementing features such as password hashing and verification with signup and login services.
You went on to use the [JWT-GO](https://pkg.go.dev/github.com/dgrijalva/jwt-go) package to build a token generator and validator which was used in a middleware to validate user tokens. You can improve on this app by adding more APIs and exploring various ways of authentication. 
###Check the [repository] (https://github.com/Joojo7/user-athentication-golang) for this tutorial out!