Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant role-based JWT security in Go server

Tags:

go

jwt

I'm writing a Go web API which uses JWT for tokens/auth etc. and I was wondering if there was a more elegant way of providing varying levels of access to my route handlers than the following?

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    jwt "gopkg.in/appleboy/gin-jwt.v2"
)

var (
    users = map[string]byte{
        "admin":    1,
        "manager":  2,
        "employee": 3,
    }
)

func main() {
    router := gin.Default()
    group := router.Group("/v1")

    jwtMiddleware := &jwt.GinJWTMiddleware{
        Realm:         "realm",
        Key:           []byte("password"),
        Authenticator: authenticate,
        PayloadFunc:   payload,
    }

    group.Use(jwtMiddleware.MiddlewareFunc())
    group.GET("/refreshToken", jwtMiddleware.RefreshHandler)
    group.GET("/hello", hello)

    router.POST("/login", jwtMiddleware.LoginHandler)
    router.Run(":1234")
}

func hello(c *gin.Context) {
    switch getRoleFromContext(c) {
    case 1:
        helloAdmin(c)
    case 2:
        helloManager(c)
    case 3:
        helloEmployee(c)
    default:
        c.AbortWithStatus(http.StatusForbidden)
    }
}

func helloAdmin(c *gin.Context)    { c.String(http.StatusOK, "Hello, admin!") }
func helloManager(c *gin.Context)  { c.String(http.StatusOK, "Hello, manager!") }
func helloEmployee(c *gin.Context) { c.String(http.StatusOK, "Hello, employee!") }

func authenticate(email string, password string, c *gin.Context) (string, bool) {
    if _, ok := users[email]; ok {
        return email, true
    }

    return "", false
}

func payload(email string) map[string]interface{} {
    return map[string]interface{}{
        "role": users[email],
    }
}

func getRoleFromContext(c *gin.Context) (role byte) {
    claims := jwt.ExtractClaims(c)

    rawRole, ok := claims["role"]
    if !ok {
        c.AbortWithStatus(http.StatusForbidden)
    }

    floatRole, ok := rawRole.(float64)
    if !ok {
        c.AbortWithStatus(http.StatusForbidden)
    }

    return byte(floatRole)
}

In my real code (consisting of only 18 or so routes), I've created a Routes struct, which provides handlers for all of the routes my API provides. Within that, I'm performing the above switching logic to specialised Route structs, which ensures only administrative users can perform specific functions, while other functions are callable by multiple types of users, just with different behaviour.

I've tried throwing together middleware-based auth as follows:

func roleCheckerMiddleware(roles ...byte) gin.HandlerFunc {
    return func(c *gin.Context) {
        contextRole := getRoleFromContext(c)
        for _, role := range roles {
            if contextRole == role {
                c.Next()
                return
            }
        }

        c.AbortWithStatus(http.StatusForbidden)
    }
}

...and applying it as follows:

adminGroup := group.Group("/")
adminGroup.GET("/hello", helloAdmin)
adminGroup.Use(roleCheckerMiddleware(1))

managerGroup := group.Group("/")
managerGroup.GET("/hello", helloManager)
managerGroup.Use(roleCheckerMiddleware(2, 1))

employeeGroup := group.Group("/")
employeeGroup.GET("/hello", helloEmployee)
employeeGroup.Use(roleCheckerMiddleware(3, 2, 1))

...but the obvious duplication of the /hello route means this method isn't (as far as I'm aware) possible, without the introduction of groupings for /admin, /manager and /employee. I'd love to be shown otherwise though as this feels more elegant that my switching logic!

like image 381
Rob Avatar asked Nov 18 '16 14:11

Rob


2 Answers

  • I hope its not too late, my response. Casbin resolves this issue nicely.
    • In addition, a very simple tutorial explaining the basics can be found at zupzup.org and https://doc.xuwenliang.com/docs/casbin/188

I find working with it using JWT and/or Session/Cookie based authentication mechanisms also very simple as. I hope this helps.

like image 156
Erasmus Mann Larbi Avatar answered Oct 20 '22 00:10

Erasmus Mann Larbi


Just Use github.com/casbin/casbin for a better precision in ur authorization and authentication middleware because it helped me a lot too.

And for more explanatory example of to use it; Use https://zupzup.org/casbin-http-role-auth/

like image 32
McWally Avatar answered Oct 19 '22 23:10

McWally