Gin: Middleware c.next() ignored?

Created on 14 May 2015  路  6Comments  路  Source: gin-gonic/gin

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()

    }
}
question

Most helpful comment

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()

All 6 comments

@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 :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iiinsomnia picture iiinsomnia  路  3Comments

lilee picture lilee  路  3Comments

mastrolinux picture mastrolinux  路  3Comments

olegsobchuk picture olegsobchuk  路  3Comments

Bloomca picture Bloomca  路  3Comments