go – Implementing ROLE with Gin-JWT-ThrowExceptions

Exception or error:

I’m working with framework GIN and Gin-JWT in Golang.
So far so good, I was able to authorize and authenticate my REST API with JWT following the example in Gin-JWT package.

I’m trying now to implement some kind of Role in my API.
The flow would be:

  1. Login and auth
  2. Create the JWT with inside the userID and the RoleID
  3. When I call a REST API I confront the role associated to the API with the RoleID in JWT to authorized

So far I have this in my main:

jwtAfp := InitJwtMiddleware(db)
afb := r.Group("api/v1/afb")
afb.Use(jwtAfp.MiddlewareFunc())
afb.GET("/ping", afbController.Ping)

and this for the InitJwtMiddleware using Gin-JWT

func InitJwtMiddleware(db *gorm.DB) *jwt.GinJWTMiddleware {
return &jwt.GinJWTMiddleware{
    Realm:      "afb",
    Key:        []byte("secret pwd"),
    Timeout:    time.Hour,
    MaxRefresh: time.Hour,
    PayloadFunc: func(data interface{}) jwt.MapClaims {
        if v, ok := data.(*model.User); ok {
            return jwt.MapClaims{
                "afb": v.ID,
            }
        }
        return jwt.MapClaims{}
    },
    Authenticator: func(c *gin.Context) (interface{}, error) {
        var loginVals login
        if err := c.Bind(&loginVals); err != nil {
            return "", jwt.ErrMissingLoginValues
        }
        email := loginVals.Username
        password := loginVals.Password
        var u model.User
        db.Where("email = ?", email).First(&u)
        if service.CheckPasswordHash(password, u.Password) {
            return &u, nil
        }

        return nil, jwt.ErrFailedAuthentication
    },
    Authorizator: func(data interface{}, c *gin.Context) bool {
        claims := jwt.ExtractClaims(c)
        v, ok := data.(float64)
        if ok && v == claims["afb"] {
            return true
        }
        return false
    },
    Unauthorized: func(c *gin.Context, code int, message string) {
        c.JSON(code, gin.H{
            "code":    code,
            "message": message,
        })
    },

    TokenHeadName: "Bearer",
    TimeFunc: time.Now,
}
}

I would like to add the checking on the Role in the Authorizator section but I’m struggling on how i can do this.
I come up with passing in the InitJwtMiddleware(db) function also the role, this will work but I don’t like the idea to “instaziate” a GinJWTMiddleware for each ROLE/API. Or if I could know inside the middleware which function (controller) will be called later I can then figure out if authorize or not. But even this solutin sound awkward to me. I think there will be a most elegant solution, any ideas?

How to solve:

You can try this:

https://github.com/kyfk/gin-jwt

It’s the simplest auth[orization/entication] library.

The VerifyPerm function could be helpful for role management.

There’s a complete example

func main() {
    auth, err := jwt.New(jwt.Auth{
        SecretKey: []byte("must change here"),

        // Authenticator authenticates a request and return jwt.MapClaims
        // that contains a user information of the request.
        Authenticator: func(c *gin.Context) (jwt.MapClaims, error) {
            var loginForm LoginForm
            if err := c.ShouldBind(&loginForm); err != nil {
                return nil, jwt.ErrorAuthenticationFailed
            }

            u, ok := authenticate(req.Username, req.Password)
            if ok {
                return nil, jwt.ErrorAuthenticationFailed
            }

            return jwt.MapClaims{
                "username": u.Username,
                "role":     u.Role,
            }, nil
        },

        // UserFetcher takes a jwt.MapClaims and return a user object.
        UserFetcher: func(c *gin.Context, claims jwt.MapClaims) (interface{}, error) {
            username, ok := claims["username"].(string)
            if !ok {
                return nil, nil
            }
            return findByUsername(username)
        },
    })

    // some lines

    e.Use(jwt.ErrorHandler)

    // issue authorization token
    e.POST("/login", auth.AuthenticateHandler)

    // refresh token expiration
    e.POST("/auth/refresh_token", auth.RefreshHandler)

    // role management
    e.GET("/operator/hello", Operator(auth), SayHello) // this is only for Operator
    e.GET("/admin/hello", Admin(auth), SayHello) // this is only for Admin
}

func Operator(m jwt.Auth) gin.HandlerFunc {
    return m.VerifyPerm(func(claims jwt.MapClaims) bool {
        return role(claims).IsOperator()
    })
}

func Admin(m jwt.Auth) gin.HandlerFunc {
    return m.VerifyPerm(func(claims jwt.MapClaims) bool {
        return role(claims).IsAdmin()
    })
}

Leave a Reply

Your email address will not be published. Required fields are marked *