0
votes

I'm reading the example folder for JWT i'm a bit unsure how things work for validating the token.

func ExampleNewWithClaims_customClaimsType() {
    mySigningKey := []byte("AllYourBase")

    type MyCustomClaims struct {
        Foo string `json:"foo"`
        jwt.StandardClaims
    }

    // Create the Claims
    claims := MyCustomClaims{
        "bar",
        jwt.StandardClaims{
            ExpiresAt: 15000,
            Issuer:    "test",
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    ss, err := token.SignedString(mySigningKey)
    fmt.Printf("%v %v", ss, err)
    //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c <nil>
}

Here it's straight forward and the token is being signed here token.SignedString(mySigningKey) using "mySigningKey"

Now there unparsing is much less clear for me :

func ExampleParseWithClaims_customClaimsType() {
    tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"

    type MyCustomClaims struct {
        Foo string `json:"foo"`
        jwt.StandardClaims
    }

    // sample token is expired.  override time so it parses as valid
    at(time.Unix(0, 0), func() {
        token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte("AllYourBase"), nil
        })

        if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
            fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt)
        } else {
            fmt.Println(err)
        }
    })

    // Output: bar 15000
}

Is order to validate that the signature of the token string given back by the client is valid would you need to

  • decode the claim ( done in &MyCustomClaims{} )
  • validate that the signature part of the decoded claim is valid against the " pub key included in the token" using token.Valid.

But the example is just decoding the key and by "magic" the return is the secret/signing key?

It doesn't make sense to me at all. Also returning a valid claim for a public key is useless as it could have been done by any private key.

What am i missing ?

1
The function passed as the last argument to ParseWithClaims returns the key. The key is not part of the token (you're correct, that would make the token utterly pointless).Peter

1 Answers

4
votes

Validation is not done by the public key included in the token.

HS256 is symmetric, so whatever key you used to sign the token, you have to use the same key to validate the signature, and that's what's happening. The function passed to ParseWithClaims is returning the same key used to sign the token.

If an asymmetric signing algorithm was used, you'd use the private key to sign the token, and then the public key to validate it, and that nested function would have to return the public key so that ParseWithClaims can validate it.

It sounds like the part that confused you is:

jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte("AllYourBase"), nil
        })

The nested function passed to ParseWithClaims is supposed to inspect the token passed, and return the correct key that can be used to verify the signature. For HS256, it is the same as the key used to sign it. For RSxxx, it'd be the public key, and which public key it should return can be retrieved from the token passed in. It usually contains a public key id so you can select the correct key.