Building a Web Server with Golang, Gin, MySQL, Redis, JWT, and Session Authentication

Building a Web Server with Golang, Gin, MySQL, Redis, JWT, and Session Authentication

In this tutorial, we will create a web server using Golang, the Gin framework, MySQL for the database, Redis for session management, and JWT for secure authentication.

Project Setup

Step 1: Create Project Directory and Initialize

First, create a project directory and initialize it.

mkdir myapp
cd myapp
go mod init myapp

Step 2: Install Dependencies

Install the necessary packages.

go get github.com/gin-gonic/gin
go get github.com/jinzhu/gorm
go get github.com/jinzhu/gorm/dialects/mysql
go get github.com/go-redis/redis/v8
go get github.com/dgrijalva/jwt-go
go get github.com/gin-contrib/sessions
go get github.com/gin-contrib/sessions/redis

Code Implementation

Step 3: Project Structure

Set up the project structure as follows:

myapp/
├── main.go
├── models/
│ └── user.go
├── handlers/
│ └── auth.go
└── middleware/
└── auth.go

Step 4: Define the User Model

Create the models/user.go file and define the User model.

package models

import "github.com/jinzhu/gorm"

type User struct
{
gorm.Model
Username string `json:"username" gorm:"unique"`
Password string `json:"password"`

}

Step 5: Set Up Database Connection

In main.go, set up connections to MySQL and Redis, and initialize the Gin server.

package main

import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/go-redis/redis/v8"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/redis"
"log"
"myapp/models"
"myapp/handlers"
"context"

)

var db *gorm.DB
var rdb *redis.Client
var err error

func main()
{
db, err = gorm.Open("mysql", "root:password@tcp(127.0.0.1:3306)/mydb?charset=utf8&parseTime=True&loc=Local")
if err != nil {
log.Fatal(err)
}
defer db.Close()

db.AutoMigrate(&models.User{})

rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
_, err = rdb.Ping(context.Background()).Result()
if err != nil {
log.Fatal(err)
}

r := gin.Default()

store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
r.Use(sessions.Sessions("mysession", store))

r.POST("/login", handlers.Login)
r.POST("/signup", handlers.Signup)
r.GET("/profile", handlers.Profile)

r.Run(":8080")
}

Step 6: Create Handlers

Define the authentication handlers in handlers/auth.go.

package handlers

import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/dgrijalva/jwt-go"
"github.com/gin-contrib/sessions"
"golang.org/x/crypto/bcrypt"
"time"
"net/http"
"myapp/models"

)

var jwtKey = []byte("my_secret_key")

type Claims struct {
Username string `json:"username"`
jwt.StandardClaims
}

func Signup(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
user.Password = string(hashedPassword)

if err := models.DB.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"message": "User registered successfully"})
}

func Login(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

var dbUser models.User
if err := models.DB.Where("username = ?", user.Username).First(&dbUser).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
return
}

if err := bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(user.Password)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"})
return
}

expirationTime := time.Now().Add(5 * time.Minute)
claims := &Claims{
Username: user.Username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

session := sessions.Default(c)
session.Set("token", tokenString)
session.Save()

c.JSON(http.StatusOK, gin.H{"token": tokenString})
}

func Profile(c *gin.Context) {
session := sessions.Default(c)
tokenString := session.Get("token")
if tokenString == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Not authenticated"})
return
}

claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString.(string), claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})

if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Not authenticated"})
return
}

c.JSON(http.StatusOK, gin.H{"username": claims.Username})
}

Step 7: Create Middleware

Define the authentication middleware in middleware/auth.go.

package middleware

import (
"github.com/gin-gonic/gin"
"github.com/gin-contrib/sessions"
"net/http"
"github.com/dgrijalva/jwt-go"
"myapp/handlers"

)

func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
tokenString := session.Get("token")
if tokenString == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Not authenticated"})
c.Abort()
return
}

claims := &handlers.Claims{}
token, err := jwt.ParseWithClaims(tokenString.(string), claims, func(token *jwt.Token) (interface{}, error) {
return handlers.jwtKey, nil
})

if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Not authenticated"})
c.Abort()
return
}

c.Set("username", claims.Username)
c.Next()
}
}

Step 8: Run and Test the Server

Run the server and test the endpoints.

go run main.go

You can use Postman or curl to test the API endpoints.

  • Signup:curl -X POST http://localhost:8080/signup -H "Content-Type: application/json" -d '{"username": "user1", "password": "password"}'
  • Login:curl -X POST http://localhost:8080/login -H "Content-Type: application/json" -d '{"username": "user1", "password": "password"}'
  • Profile:curl -X GET http://localhost:8080/profile --cookie "mysession=<your_session_id>"

By following these steps, you can build a web server with Golang, Gin, MySQL, Redis, JWT, and session authentication. If you have any questions, feel free to ask!

Read more