golang-jwt / jwt

Go implementation of JSON Web Tokens (JWT).
https://golang-jwt.github.io/jwt/
MIT License
6.96k stars 335 forks source link

Add option to change time precision for creation/parsing tokens. #400

Open ajermaky opened 1 month ago

ajermaky commented 1 month ago

We can currently change the precision of timestamps getting serialized by modifying TimePrecision here. However we are running into an issue where we have to serialize/validate different jwt tokens of various precisions (i.e. seconds, milliseconds).

As a work around, we are creating our own version of jwt.RegisteredClaims that serializes/deserializes times based on the precision that we want. However it would be nice if we can include a time precision option in jwt.New or jwt.ParseWithClaims

oxisto commented 1 month ago

I had a similar feature in mind a while ago. This was previously not possible, but recently we changed the way the parser/validator works and both can now be supplied with options that are accessible in the respective (de)serialisation functions, so this might work.

You could probably take some hints from https://github.com/golang-jwt/jwt/pull/301. Specifically you need to apply the token options to the created token like this (which is not yet in main): https://github.com/golang-jwt/jwt/pull/301/files#diff-2feccbe0109db2f6b93af0c4afed26af571a61438266ebd8690bf669c75d2874R63-R66

Just to add, I am not quite sure why, but after writing these lines I think there was still some kind of issue with this we could not work around... But I cannot remember, so feel free to try it ;)

ajermaky commented 1 month ago

makes sense! Will take a crack and give and update!

oxisto commented 1 month ago

Hmm I remember now why this probably won’t work. You will not have access to the options in the unmarshal func of NumericDate. I would be interested in how you solved this problem in your workaround.

ajermaky commented 1 month ago

You will not have access to the options in the unmarshal func of NumericDate.

Can Confirm, this is the issue i'm hitting against!

I am mainly working with the idea to add a precision field to NumericDate and remove any calls to truncate the time until it gets fetched, i.e.

type NumericDate struct {
    t time.Time
    precision time.Duration
}

func (d *NumericDate) Time() time.Time{
    return d.t.Truncate(d.precision)

}

But like you said, parser won't be able to control the unmarshal issue. The one idea I had was to update the claims interface as:

type Claims interface {
    GetExpirationTime() (*NumericDate, error)
    GetIssuedAt() (*NumericDate, error)
    GetNotBefore() (*NumericDate, error)
    GetIssuer() (string, error)
    GetSubject() (string, error)
    GetAudience() (ClaimStrings, error)
    SetPrecision(time.Duration)
}

And registered claims being something like:


func (c RegisteredClaims) SetPrecision(precision time.Duration) {
    c.ExpiresAt.precision = precision
    c.NotBefore.precision = precision
    c.IssuedAt.precision = precision
}

And inside the parser, we call it before getting unmarshalled i.e.

token.Claims = claims
prec = // if options nil, use default time.Second
token.Claims.SetPrecision(prec)
...
err = json.Unmarshal(claimBytes, &claims)

That looks like it may work, but it feels a bit wonky. I think specifically main issues:

I do think adding a precision field on the NumericDate object would be a step in the right direction, as that would give us more control, but not seeing a way for the parser to control that.

Curious on your thoughts!

oxisto commented 1 week ago

we are probably stuck here until the v2 version of json arrives with options. See https://pkg.go.dev/github.com/go-json-experiment/json#Options