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