# Implementing JWT Authentication with Redis and Go

By [Mozes721](https://paragraph.com/@mozes721) · 2024-07-05

---

![](https://storage.googleapis.com/papyrus_images/a1379615daf2c257877bb5bf54b22424a5377d9314d843dd6dd94d5539f1b8f0.png)

Redis is an in memory data structure store often used as a cache and message broker but can as well be used as a primary database.

Redis is well suited for JWT authentication tokens due to Speed, Scalability, TTL(Time To Live), Session Storage.

I will use own repository to showcase how have I used it and if you want to follow video format you can check out bellow **YouTube** videos.

[![]({{DOMAIN}}/editor/youtube/play.png)](https://www.youtube.com/watch?v=SQrsDZU_D5k)

[![]({{DOMAIN}}/editor/youtube/play.png)](https://www.youtube.com/watch?v=NissLXyZ2Zw)

### Introduction

In JWT authentication like mine it makes sense if you have a primary database like PostgreSQL, Mongo or Firebase(like in my example).

Be sure to run command

    go get github.com/redis/go-redis/v9
    

Ones installed figure out if you want to run Redis in a Docker Image or PaaS provider like Upstash👇

[https://console.upstash.com/login](https://console.upstash.com/login)

You can use Docker to run Redis.

> docker run — name recepie -p 6379:6379 -d redis:latest

In a nutshell my frontend is with React but backend is Go with Gin Web Framework main database Firebase and Redis will save userID with AuthToken ones logged in from frontend.

### Architecture & Code

Architecures change based on implementation This is how I have approached it to make it organized.

![](https://storage.googleapis.com/papyrus_images/0cd43079a732738f7191515b5e2a67153540a6305d6733e5ada5976d326545c8.png)

* * *

#### Application Configuration

**LoadConfigurations**: Set up initial configurations, including connecting to Firebase and Redis.

    func (a *Application) LoadConfigurations() error {
     ctx := context.Background()
    
     fireClient, err := GetFirestoreClient(ctx)
     if err != nil {
      return err
     }
     a.FireClient = fireClient
    
     fireAuth, err := GetAuthClient(ctx)
     if err != nil {
      return err
     }
     a.FireAuth = fireAuth
    
     // Redis env variable depending if PaaS server provided if not 6379 port used. 
     // So basically Docker image.
     a.RedisPort = envy.Get("REDIS_SERVER", "localhost:6379")
    
     redisClient, err := RedisConnect(a.RedisPort)
     if err != nil {
      return err
     }
    
     a.RedisClient = redisClient
    
     a.ListenPort = envy.Get("PORT", "8080")
    
     return nil
    }
    

**RedisConnect Function**: Connect to Redis, handling both Docker and PaaS setups

    func redisClientPort(port string, envExists bool) (*redis.Client, error) {
        if envExists {
           opt, err := redis.ParseURL(port)
           if err != nil {
              return nil, fmt.Errorf("failed to parse Redis URL: %w", err)
           }
           return redis.NewClient(opt), nil
        }
    
        return redis.NewClient(&redis.Options{
           Addr:     port,
           Password: "",
           DB:       0,
        }), nil
    }
    
    func RedisConnect(port string) (*redis.Client, error) {
        _, ok := os.LookupEnv(port)
        client, err := redisClientPort(port, ok)
        if err != nil {
           return nil, fmt.Errorf("failed to ping Redis server: %w", err)
        }
    
        ping, err := client.Ping(context.Background()).Result()
        if err != nil {
           return nil, fmt.Errorf("failed to ping Redis server: %w", err)
        }
    
        fmt.Println("Ping response from Redis:", ping)
        return client, nil
    }
    

**Start Function**: Initialize the Gin router and set up routes and middleware.

    func Start(a *app.Application) error {
        router := gin.New()
    
        router.Use(cors.New(md.CORSMiddleware()))
    
        api.SetCache(router, a.RedisClient)
    
        api.SetRoutes(router, a.FireClient, a.FireAuth, a.RedisClient)
    
        err := router.Run(":" + a.ListenPort)
        if err != nil {
           return err
        }
    
        return nil
    }
    

**SetCache Function**: Define endpoints for setting cache and other requests handled by **Firebase**.

    // api/controller.go
    func SetCache(router *gin.Engine, client *redis.Client) {
        router.POST("/set-cache", func(c *gin.Context) {
           setUserCache(c, client)
        })
    
        router.GET("/check-expiration", func(c *gin.Context) {
           checkTokenExpiration(c, client)
        })
    
    }
    
    func SetRoutes(router *gin.Engine, client *firestore.Client, auth *auth.Client, redisClient *redis.Client) {
        router.OPTIONS("/*any", func(c *gin.Context) {
           c.Status(http.StatusOK)
        })
        
        // In Gin Use means that it's required
        router.Use(func(c *gin.Context) {
           authToken := getUserCache(c, redisClient)
           md.AuthJWT(auth, authToken)(c)
        })
    
        router.GET("/", func(c *gin.Context) {
           showRecepies(c, client)
        })
    
        router.POST("/", func(c *gin.Context) {
           addRecepie(c, client)
        })
    

In SetRouters we do main requests to alter db data. **AuthJWT** is set by client side on **Firebase** to authenticate any request made to database.

AuthToken is what we will be talking about and it is passed in to AuthJWT to authenticate or deny user of any interaction.

* * *

Next up is to set up main GET, SET actions including TTL for session management.

    // models/cache.go
    type UserCache struct {
        UserID    string `redis:"UserID"`
        AuthToken string `redis:"AuthToken"`
    }
    

The above struct is the only important one to parse incoming data from React. ☝️

#### Handling Cache Operations

**Get User Cache**: Retrieve user cache from Redis.

    // api/cache.go
    func getUserCache(ctx *gin.Context, client *redis.Client) string {
        userID := ctx.Query("userID")
        authToken, err := models.GetUserCacheToken(ctx, client, userID)
        if err != nil {
           log.Printf("Issues retriving  Cached Token %v", err)
           return ""
        }
    
        return authToken
    }
    
    // models/cache.go
    func GetUserCacheToken(ctx *gin.Context, client *redis.Client, userID string) (string, error) {
     key := fmt.Sprintf("user:%s", userID)
     cache, err := client.HGetAll(ctx, key).Result()
     if err != nil {
      return "", fmt.Errorf("failed to get cache: %v", err)
     }
    
     authToken, ok := cache["AuthToken"]
     if !ok {
      return "", fmt.Errorf("AuthToken not found in cache")
     }
    
     return authToken, nil
    }
    

**Set User Cache**: Set user cache in Redis with TTL.

    // api/cache.go
    func setUserCache(ctx *gin.Context, client *redis.Client) {
        var userCache models.UserCache
    
        err := models.UnmarshallRequestBodyToAPIData(ctx.Request.Body, &userCache)
        if err != nil {
           ctx.JSON(http.StatusBadRequest, gin.H{
              "error": "Unable to parse data",
           })
           return
        }
    
        key := fmt.Sprintf("user:%s", userCache.UserID)
        _, notExists := client.HGetAll(ctx, key).Result()
    
        if notExists == nil {
           userCache.SetCachedToken(ctx, client, key)
           return
        }
    
    }
    
    // models/cache.go
    
    func (c *UserCache) SetCachedToken(ctx *gin.Context, client *redis.Client, key string) {
     fields := map[string]interface{}{
      "UserID":    c.UserID,
      "AuthToken": c.AuthToken,
     }
     err := client.HSet(ctx, key, fields).Err()
     if err != nil {
      log.Printf("Issues setting Cached Token %v", err)
     }
    
     client.Expire(ctx, key, 7*24*time.Hour)
    
    }
    

> If you are interested in React section let me know otherwise Github repo will be listed bellow.

> As on React login through Firebase it creates a user with authToken and passes to Go backend if exists ignore otherwise create.

![](https://storage.googleapis.com/papyrus_images/a1e9cd5ed1874354bb942db528d2adcd6bfcf99aee63b05b8aa44877078afef8.png)

#### Check Token Expiration

**Check Token Expiration**: Check if the token has expired.

    
    // api/cache.go
    func checkTokenExpiration(ctx *gin.Context, client *redis.Client) {
        userID := ctx.Query("userID")
        key := fmt.Sprintf("user:%s", userID)
    
        ttl, err := client.TTL(ctx, key).Result()
        if err != nil {
           ctx.JSON(http.StatusInternalServerError, gin.H{
              "error": "Failed to get TTL",
           })
           return
        }
    
        expired := ttl <= 0
    
        ctx.JSON(http.StatusOK, expired)
    }
    

#### Conclusion

This setup provides a robust structure for managing JWT authentication with Redis in a Go application, ensuring efficient session management and token validation. If there are any questions feel free to ask(or ask GPT) my repo you can find bellow.

[https://github.com/Mozes721/RecipesApp](https://github.com/Mozes721/RecipesApp)

---

*Originally published on [Mozes721](https://paragraph.com/@mozes721/implementing-jwt-authentication-with-redis-and-go)*
