I came across an interesting issue today. I wrote a super basic authentication middleware, which executes c.next() if user is logged in and simply returns when user is not logged in.
However even when I do return before c.next() the actual handler still executes. Is this the expected behavior? If so, why?
func Authentication() gin.HandlerFunc {
return func(c *gin.Context) {
tokenHeader := c.Request.Header.Get("Token")
if len(tokenHeader) == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"Error": "Token header is missing"})
return
}
token, err := jwt.Parse(tokenHeader, func(t *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"])
}
if int64(t.Claims["exp"].(float64)) < time.Now().Unix() {
return nil, ErrorInvalidToken
}
return []byte(config.Config.Key), nil
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"Error": err.Error()})
return
}
c.Set("UserID", int64(token.Claims["id"].(float64)))
c.Set("AuthToken", token)
// <----
// THIS NEXT IS COMPLETELY POINTLESS. EVEN IF I return BEFORE IT, THE HANDLER WILL STILL EXECUTE
// ---->
c.Next()
}
}
@nazwa exactly, that c.Next() is pointless, remove it.
c.Next() is used in order to execute code after the Next handlers.
Example:
func latency(c *gin.Context) {
time := time.Now()
// before
c.Next() // < the rest of handlers in the chain are executed here!
// after
now := time.Now()
diff = now.Sub(time)
fmt.Println(diff)
}
You have to use Abort() or AbortWithStatus() in order to stop the chain
func Authentication() gin.HandlerFunc {
return func(c *gin.Context) {
tokenHeader := c.Request.Header.Get("Token")
if len(tokenHeader) == 0 {
c.Abort()
c.JSON(http.StatusUnauthorized, gin.H{"Error": "Token header is missing"})
return
}
token, err := jwt.Parse(tokenHeader, func(t *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", t.Header["alg"])
}
if int64(t.Claims["exp"].(float64)) < time.Now().Unix() {
return nil, ErrorInvalidToken
}
return []byte(config.Config.Key), nil
})
if err != nil || !token.Valid {
c.Abort()
c.JSON(http.StatusUnauthorized, gin.H{"Error": err.Error()})
return
}
c.Set("UserID", int64(token.Claims["id"].(float64)))
c.Set("AuthToken", token)
}
notice the c.Abort()
See how the default logger is implemented:
https://github.com/gin-gonic/gin/blob/develop/logger.go#L50-L51
Complicated flows tested in the unit tests: https://github.com/gin-gonic/gin/blob/develop/middleware_test.go#L44-L72
Great, thanks for clarifying it. I still have a feeling that this is not the ideal approach, but at least now I know what's up :)
Most helpful comment
notice the
c.Abort()