• With issues:
  • Use the search tool before opening a new issue.
  • Please provide source code and commit sha if you found a bug.
  • Review existing issues and provide feedback or react to them.

Description

I have an API which I have created and I have some endpoints protected. The problem I am facing now on the client making a request is that the first request comes through with the Authorization header provided but a second request is blocked because Authorization is not present. I can confirm that Authorization is present and worked perfectly when I was running Typescript till I recreated the endpoints in Go with Gin.

How to reproduce

  • Call estimate endpoint from client (iOS app) response succeceds
  • Make a second call from Client (iOS app) response failed because it is not taking the Authorization header which contains token
package main

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

type App struct {
    Router   *gin.Engine
    Gateman  *gateman.Gateman
    Database *mongo.Client
}

func (a *App) StartApp() {

    err := godotenv.Load()
    if err != nil {
        fmt.Printf("Could not load .env \n")
    }

    a.Database = database.DB
    a.Router = gin.New()

    a.Gateman = middleware.Gateman()
    a.Router.Use(gin.Recovery())

    a.Router.Use(middleware.DefaultHelmet())

    a.Router.Use(middleware.GinContextToContextMiddleware())
    a.Router.Use(middleware.RequestID(nil))
    a.Router.Use(middleware.ErrorHandler())
    a.Router.Use(middleware.Logger("package-service"))

    connection, err := amqp091.Dial(os.Getenv("AMQP_URL"))

    if err != nil {
        log.Fatal(fmt.Printf("Error on dial %v\n", err.Error()))
    }
    routes.Routes(a.Router, database.GetDatabase(a.Database), a.Gateman, connection)

}

func (a *App) Run(addr string) {
    logs := log.New(os.Stdout, "package-service", log.LstdFlags)

    server := &http.Server{
        Addr:         addr,
        Handler:      a.Router,
        ErrorLog:     logs,
        IdleTimeout:  120 * time.Second, // max time for connections using TCP Keep-Alive
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    go func() {
        if err := server.ListenAndServe(); err != nil {
            logs.Fatal(err)
        }
    }()

    // trap sigterm or interrupt and gracefully shutdown the server
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)
    signal.Notify(c, os.Kill)

    sig := <-c
    logs.Println("Recieved terminate, graceful shutdown", sig)
    tc, _ := context.WithTimeout(context.Background(), 30*time.Second)
    server.Shutdown(tc)
}

func Routes(r *gin.Engine, db *mongo.Database, g *gateman.Gateman, conn *amqp091.Connection) {

    atHandler := pc.NewPackagesController(ps.NewPackagesService(pr.NewPackagesRepository(db)), g, events.NewEventEmitter(conn))

    r.Use(CORS())
    v1 := r.Group("/api/v1/package")
    {
        v1.POST("/query", GraphqlHandler(db, directives.NewDirectivesManager(g)))
        v1.GET("/", PlaygroundHandler(db))
        v1.POST("/", g.Guard([]string{"user"}, nil), atHandler.Create)
        v1.POST("/estimate", g.Guard([]string{"user"}, nil), atHandler.Estimate)
        v1.PUT("/:packageID", g.Guard([]string{"user", "admin"}, nil), atHandler.Update)
        v1.PUT("/:packageID/assign", g.Guard([]string{"admin"}, nil), atHandler.Assign)
        v1.POST("/:packageID/cancel", g.Guard([]string{"user", "admin"}, nil), atHandler.CancelRequest)
        v1.POST("/:packageID/complete", g.Guard([]string{"admin"}, nil), atHandler.Complete)
        v1.POST("/:packageID/reject", g.Guard([]string{"admin"}, nil), atHandler.RejectRequest)

        v1.GET("/healthz", atHandler.GetHealth)

    }
    r.GET("/", atHandler.GetUP)
}

func CORS() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}

func main() {
    start := App{}
    start.StartApp()
    start.Run(":3009")
}

Expectations

All endpoints that are Guarded simply checks the header for Authorization and if provided in the request, it should be successful

Actual result

First request succeed /estimate Second request / POST request fails to accept Authorization header

Also irrespective of what the first post request is, the second post request just never accept the Authorization header

Also need to mention that I do not have this issue with postman. Both request run independently but using another client for the request, gives this problem

Another point, calling a single endpoint multiple time, works fine, calling a different one is where the header is rejected

Environment

  • go version: 1.19
  • gin version (or commit ref): v1.8.1
  • operating system: Mac and iOS mobile

Comment From: AdieOlami

I think I can pin the problem down now. A weird problem. In my request, I am using /api/v1/package this was causing a 307 redirect. It expected me to use /api/v1/package/ which is weird.