Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dgrijalva/jwt-go can cast claims to MapClaims but not StandardClaims?

Tags:

go

jwt

jwt-go

I am creating the token with the following code

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    Subject: string(user.Id),
})

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

and trying to parse them with the following code

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, UnauthorizedError
    }

    return []byte("secret"), nil
})
if err != nil {
    return -1, UnauthorizedError
}

if !token.Valid {
    return -1, UnauthorizedError
}

claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
    return -1, UnauthorizedError
}

logrus.Info(claims)

Why can I not cast my claims to StandardClaims and access claims.Subject?

like image 593
Jamie Davenport Avatar asked Sep 22 '18 19:09

Jamie Davenport


1 Answers

Why can I not cast my claims to StandardClaims and access claims.Subject?

Conceptually, this is not possible because the jwt.Parse function by default parses claims into an instance of jwt.MapClaims. This is a fundamentally different data structure to jwt.StandardClaims; there is no way the compiler can automatically convert between the two using a simple type conversion as they represent the data differently.

Resolution

The library provides the ParseWithClaims function, which allows you to specify your own implementer of the jwt.Claims interface for claims to be decoded into. You can pass an instance of jwt.StandardClaims. For example:

token, err := jwt.ParseWithClaims(
    tokenString, &jwt.StandardClaims{},
    func(token *jwt.Token) (interface{}, error) {
        // ...
    },
)

If possible, the claims will be parsed and decoded into the variable token.Claims. The underlying (dynamic1) type of the value stored into this variable will be *jwt.StandardClaims. This can be used in a type assertion to recover the standard claims from the interface type:

claims, ok := token.Claims.(*jwt.StandardClaims)
if !ok {
    // handle type assertion failure
}
// do something with "claims"

Let's dig into the language specification and library definition some more to provide a more rigorous assessment of this claim.

Background understanding of package types

jwt.MapClaims is a defined type with underlying type map[string]interface{} (code).

jwt.StandardClaims is a defined struct type (code):

type StandardClaims struct {
    // Field set elided for brevity, as it is unimportant to the
    // answer.
}

Both types implement the jwt.Claims interface type (definition), so is assignable to a variable of type jwt.Claims:

type Claims interface {
    Valid() bool
}

The Token struct has a field called Claims of type jwt.Claims – any value which implements the Claims interface can be assigned to Claims.

Type assertion definition

The language spec specifies for a type assertion expression of the form x.(T) to be valid when T is not an interface type, the dynamic type1 of x must be identical to the type T. Here, you wish to evaluate the assertion x.(*jwt.StandardClaims); i.e. the asserted type is not an interface type.

The code for jwt.Parse eventually calls jwt.ParseWithClaims on the default parser, passing in an instance of jwt.MapClaims for the claims destination:

func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
    return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}

so the dynamic type of the Claims field in your resulting token is of type jwt.MapClaims. This type is different (i.e. not identical) to the type jwt.StandardClaims, because user-defined types are always different from any other type except themselves. Hence, the type assertion fails.


1Dynamic types (ref): recall in Go that interface types are fulfilled implicitly by any type which implements a super-set of the methods specified in an interface. If we define an interface of type MyInterface, the variable declaration var x MyInterface has static type (defined at compile time) MyInterface. However, at runtime, we can assign any value which implements MyInterface to x. The underlying type of the value assigned to x at any moment (the type which implements the interface) specifies the dynamic type of the variable.

like image 96
Cosmic Ossifrage Avatar answered Nov 14 '22 18:11

Cosmic Ossifrage