3

I need to retrieve the values of sub-claims from a JWT in Go.

I have (legacy) JWTs I need to parse in go, which contain a custom claim "data" which holds an Json-Object consisting of some fields (userid, username), so

{ [...standard claims]..., "data":{"id":"123", "name":"JohnDoe"} }

With using github.com/dgrijalva/jwt-go, I can parse the token and access the claims with this:

keyfunc := func(token *jwt.Token) (interface{}, error) {
    return tknkey, nil
}

tkn, err := jwt.Parse(tknStr, keyfunc)
cl, _ := tkn.Claims.(jwt.MapClaims)

This works fine for the standard claims, and I also get the field names from the Json-Sub-Object in the "data" claim, but not the field values (all empty strings). I also tried setting up structs matching the claim hierarchy (outer and inner struct), with no success.

What would be the way to access the values of the sub-claims?

Grokify
  • 15,092
  • 6
  • 60
  • 81
kiteflight
  • 120
  • 1
  • 13

2 Answers2

9

You can use jwt.MapClaims with "data": map[string]string with the following steps.

  • Steps 1.1 and 1.2 create the token
  • Steps 2.1 and 2.2 parse the token and extract the sub-claim values.

In the below example, jwt is github.com/golang-jwt/jwt/v4. Running code for this example is at github.com/grokify/goauth/examples/jwt/main.go.

Step 1.1: Create the claims

Create the custom MapClaims with a data map. Add a custom data.name property which we'll extract below.

claims := &jwt.MapClaims{
    "iss": "issuer",
    "exp": time.Now().Add(time.Hour).Unix(),
    "data": map[string]string{
        "id":   "123",
        "name": "JohnDoe",
    },
}

Step 1.2: Create the JWT

For this example, we'll use a symmetric key.

token := jwt.NewWithClaims(
    jwt.SigningMethodHS256,
    claims)

secretKey := "foobar"

tokenString, err := token.SignedString([]byte(secretKey))

Step 2.1: Parse the token and cast claims to MapClaims.

Use the secretKey again since this example uses HS256.

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return []byte(secretKey), nil
})

claims := token.Claims.(jwt.MapClaims)

Step 2.2: Extract custom sub-claim

Cast data to map[string]interface{} and cast data["name"] to string.

data := claims["data"].(map[string]interface{})
name := data["name"].(string)
Grokify
  • 15,092
  • 6
  • 60
  • 81
  • What if I need to go even one level deeper? Twilio JWT claims are like this: `"grants": { "identity": "user_name", "chat": {"service_sid": "ISxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} }` Seems like you can't use `map[string]string` in that case to build the claims – Gillespie May 22 '21 at 20:21
  • The answer was done for the question here but it's easy enough to use `map[string]interface{}`. Would you like an example? If so, I can think about editing both the question and answer to make it more generic. – Grokify May 24 '21 at 08:09
  • Might be nice to demonstrate how to go one level deeper. I figured it out - you can either use `map[string]interface{}` or just use `jwt.MapClaims{}` which is the same thing – Gillespie May 24 '21 at 14:03
  • The answer was updated a while back but wanted to respond and close this out. The example was updated to use `map[string]interface{}` the custom `data` claim and which would be similar to the Twilio example's custom `grants` claim. Neither claim is defined by RFC-7519. If you are interested in using `jwt.MapClaims{}` for the custom `data` or `grants` claim, it can be used as defined as a `map[string]interface{}`, but I think it could be misleading as the extended functions aren't relevant: https://pkg.go.dev/github.com/golang-jwt/jwt/v4#MapClaims . – Grokify Apr 22 '22 at 18:31
1

Try this

type UserData struct {
    Id   string `json:"id"`
    Name string `json:"name"`
 }

type JWTClaim struct {
    Data UserData `json:"data"`
    jwt.StandardClaims
}

token, err := jwt.ParseWithClaims(
    signedToken,
    &JWTClaim{},
    func(token *jwt.Token) (interface{}, error) {
        return []byte(jwtKey), nil
    },
)