restgojwthandlergo-gin

Go Gin Setting and Accessing context value from middleware


I am trying to set my user context, in the middleware then trying to check if user have permission in other handler functions. But for some reason when I try to access the user from context it is coming back as nils. The middleware code seems to be working, when I pass a valid jwt token, it is showing the user is being set in context in the middleware function. But as soon as I hit getCurrentUser function it says it's nil.

Here is the code: Middleware

// Middleware wraps the request with auth middleware
func Middleware(path string, sc *cfg.Server, orm *orm.ORM) gin.HandlerFunc {
    logger.Info("[Auth.Middleware] Applied to path: ", path)
    return gin.HandlerFunc(func(c *gin.Context) {
        t, err := ParseToken(c, sc)
        if err != nil {
            authError(c, err)
        } else {
            if claims, ok := t.Claims.(jwt.MapClaims); ok {
                if claims["exp"] != nil {
                    issuer := claims["iss"].(string)
                    userid := claims["jti"].(string)
                    email := claims["email"].(string)
                    if claims["aud"] != nil {
                        audiences := claims["aud"].(interface{})
                        logger.Warnf("\n\naudiences: %s\n\n", audiences)
                    }
                    if claims["alg"] != nil {
                        algo := claims["alg"].(string)
                        logger.Warnf("\n\nalgo: %s\n\n", algo)
                    }
                    if user, err := orm.FindUserByJWT(email, issuer, userid); err != nil {
                        authError(c, ErrForbidden)
                    } else {
                        if user != nil {
                            c.Request = addToContext(c, consts.ProjectContextKeys.UserCtxKey, user)
                            logger.Debug("User: ", user.ID)
                        }
                        c.Next()
                    }
                } else {
                    authError(c, ErrMissingExpField)
                }
            } else {
                authError(c, err)
            }
        }
    })
}

routes

// User routes
func User(sc *cfg.Server, r *gin.Engine, orm *orm.ORM) error {
    // OAuth handlers
    mw := auth.Middleware(sc.VersionedEndpoint("/user/:id"), sc, orm)
    g := r.Group(sc.VersionedEndpoint("/user"))
    g.Use(mw)
    g.GET("/:id", mw, user.Get(orm))
    g.PUT("/:id", mw, user.Update(orm))
    g.POST("/", user.Create(orm))

    return nil
}

handler

func Get(orm *orm.ORM) gin.HandlerFunc {
    return func(ctx *gin.Context) {
        cu := getCurrentUser(ctx)
        if ok, err := cu.HasPermission(consts.Permissions.Create, consts.EntityNames.Users); !ok || err != nil {
            ctx.String(http.StatusUnauthorized, "BAD")
        }
    }
}

addToContext:

func addToContext(c *gin.Context, key consts.ContextKey, value interface{}) *http.Request {
    return c.Request.WithContext(context.WithValue(c.Request.Context(), key, value))
}

getCurrentUser:

func getCurrentUser(ctx context.Context) *dbm.User {
    cu := ctx.Value(utils.ProjectContextKeys.UserCtxKey).(*dbm.User)
    logger.Debugf("currentUser: %s - %s", cu.Email, cu.ID)
    return cu
}

Solution

  • The problem is that you're storing the user in one context but then you're attempting to retrieve the user from another context. The value *gin.Context and the value *gin.Context.Request.Context are two separate context values.

    You're using the Request's context to store the user:

    c.Request.WithContext(context.WithValue(c.Request.Context(), key, value))
    

    And then you're using the gin context to retrieve the user:

    func getCurrentUser(ctx context.Context) *dbm.User {
        cu := ctx.Value(utils.ProjectContextKeys.UserCtxKey).(*dbm.User)
        // ...
    
    func Get(orm *orm.ORM) gin.HandlerFunc {
        return func(ctx *gin.Context) {
            cu := getCurrentUser(ctx) // here you're passing *gin.Context to the function.
            // ...
    

    So to fix that change the value that's passed in to the getCurrentUser call to:

    func Get(orm *orm.ORM) gin.HandlerFunc {
        return func(ctx *gin.Context) {
            cu := getCurrentUser(ctx.Request.Context())
            if ok, err := cu.HasPermission(consts.Permissions.Create, consts.EntityNames.Users); !ok || err != nil {
                ctx.String(http.StatusUnauthorized, "BAD")
            }
        }
    }