rak1n
rak1n

Reputation: 787

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
}

Upvotes: 2

Views: 13324

Answers (2)

Coder_H
Coder_H

Reputation: 322

You can set ContextWithFallback field to true after initializing default Gin Engine.

r := gin.Default()
r.ContextWithFallback = true

This will tell gin to extract value using gin.Context.Value() when value is not found using gin.Context.Request.Context.Value(). This way you don't need to modify your getCurrentUser function call as suggested by @mkopriva in this answer

This code here checks if gin.Engine.ContextWithFallback is enabled and gin.Request.Context() is not nil. If both the conditions are met then gin.Context.Value(key) returns gin.Context.Request.Context.Value(key).

Upvotes: 0

mkopriva
mkopriva

Reputation: 38313

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")
        }
    }
}

Upvotes: 7

Related Questions