-
Notifications
You must be signed in to change notification settings - Fork 993
Allow int types in MapClaims validations #162
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Alexander Widerberg <alexander.widerberg@cybercom.com>
How will a |
Ok, an example :) We have an API that was constructed with jwt-go 2.7.0. To not break any APIs, we simply made the following to be able to utilize the validators in v3.0.0 (map_claims.go) in a smart manner: example.go // .... package + imports skipped
type ExtJWT struct {
// ...not complete claims set, there are more claims in real struct...
ExpiresAt time.Time
Extra map[string]interface{}
}
func (e *ExtJWT) ToTest() jwt.MapClaims {
test := make(map[string]interface{}, len(e.Extra))
for k, v := range e.Extra {
test[k] = v
}
test["exp"] = e.ExpiresAt.Unix() // <-- This will not work, wrong type
//test["exp"] = float64(e.ExpiresAt.Unix()) // <-- This will work, correct type
return test
} example_test.go // .... package + imports skipped
func TestToTest(t *testing.T) {
// This should be OK. It is.... Sort of...
err := (&ExtJWT{ExpiresAt: time.Now().Add(time.Hour)}).
ToTest().Valid()
fmt.Println(err)
if err != nil {
t.Errorf("expected nil")
}
// This should fail! But it does not! Err == nil!
err = (&ExtJWT{ExpiresAt: time.Now().Add(-2 * time.Hour)}).
ToTest().Valid()
if err == nil {
t.Errorf("expected not nil")
}
} Since we have to do this to not break any internal (or external) structures, we will have the ability to test our implementations as well with wrong types.. Of course, after jwt.New -> jwt.Parse() the types will be correct, but for certain internal tests BEFORE we actually create a token (and regular testing), the validators will fail, since the values have not been marshalled yet. I know, this is a super rare condition and probably "the wrong way" of doing this, but we like being more safe than sorry ;) |
In the spirit of being safe, would you add some unit tests to this? |
It would help me understand your example test if there was a before and after of what the change unbreaks. What went from red to green? |
Sorry for the delay. I've been on vacation. @johnlockwood-wf I don't understand your question about the example test. |
@dgrijalva In the example @leetal posted, I want to understand what part is broken before this code change and thus what is fixed after. Maybe if he shows the output of go test before and after. |
I'm sort of on the fence about it. There's no real harm in adding extra tests, however, I expect I'll get support tickets from people who are confused by this. The number type thing is already a source of confusion for users of this library. |
I do think this change would add confusion. |
Oh. The issue is, the |
I don't understand the need to have the number anything but |
Adding support of some arbitrary other number types to this lib doesn't make sense. |
Actually @johnlockwood-wf. It would make sense if you used the MapClaims type internally BEFORE actually sending it to the marshaller. Since by using the MapClaims reduces the need for some internal struct i many cases. An example is OAuth2/OpenID Connect implementations where you have to do lots of work on the claims BEFORE actually creating the token. Thus, this change would benefit such cases. And in reality, a type assertion should not confuse a GO developer that much. This is such a mere change that wouldn't break a thing. It would benefit cases where you actually have to work with the claims internally (thus using timestamps as Unix-time int64´s for example). That's where i'm standing. But i think that if @dgrijalva thinks this will confuse people more, we'll just have to cast all of our internal timestamps to float64. It just feels more ugly to do that, than add an extra type assertion in jwt-go. |
@leetal I have been looking at the Valid method as if it is exclusively used for validating what has been unmarshalled from JSON. But, you're using it to validate a MapClaims before it has been marshalled. |
@johnlockwood-wf Yes, i must use it prior to marshalling the token since that will solve lots of issues later on, especially due to the nature of OAuth2/OpenID Connect. Actually there is no need to do extra cpu-cycles just to marshal when i actually can validate the claims prior to marshalling them into the token. Perhaps i'm misusing the library, but i thought it was far more giving (in my case at least) to validate the claims prior to marshal. |
My opinion on this as a user of the library is that types and their associated functions should work the same no matter how they were created. It can be very disconcerting to manually create a This is a byproduct of using a Barring a redesign of A data structure should be designed to maintain it's integrity, so that all preconditions for any functions operating on this data are valid. Basically, users should not be able to create a |
For me, storing an tokenString, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"id": 5}).SignedString(JWTSecret)
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return JWTSecret, nil
})
_, ok := token.Claims.(jwt.MapClaims)["id"].(int)
if !ok {
fmt.Println("not an int")
}
_, ok = token.Claims.(jwt.MapClaims)["id"].(float64)
if ok {
fmt.Println("is a float64")
} Is that solved with this PR? Or is there a way within the master branch to store and retrieve ints in the JWT without type conversion? |
@everdev this is a side effect of how the standard library JSON parser works. You can use the JSONNumber flag to use that library's number type, which is more flexible. Alternatively, you can define a struct type for your claims and follow that path. |
I would also love if this could get merged. For all the reasons mentioned above. But even more. In OIDC setting, now we have to make claims be float to pass validation done by this library, but then if we use same claims to make a JSON, sometimes those floats can be encoded in their exponential form. Which does not work well with some consumers which assume those numbers will always be represented as integers. See here for similar issue. Yes, exponential form is valid JSON number but it is still better to not do it if it is not necessary. So that outputs are more compatible. |
This will allow the MapClaims validators to be used with Unix timestamps generated like
time.Now().Unix()
. Prior to this PR, only json.Number and float64 types were allowed.This will allow the validation to pass when supplying int values as well.
The following types were added as allowed:
int
(architecture dependent)int64
(non-architecture dependent)