Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

Commit

Permalink
Make exp, iat, nbf claims nullable to handle zero dates correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
csstaub committed Jan 11, 2019
1 parent cbf0fd6 commit 985395e
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 19 deletions.
29 changes: 14 additions & 15 deletions jwt/claims.go
Expand Up @@ -26,13 +26,13 @@ import (

// Claims represents public claim values (as specified in RFC 7519).
type Claims struct {
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Audience Audience `json:"aud,omitempty"`
Expiry NumericDate `json:"exp,omitempty"`
NotBefore NumericDate `json:"nbf,omitempty"`
IssuedAt NumericDate `json:"iat,omitempty"`
ID string `json:"jti,omitempty"`
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Audience Audience `json:"aud,omitempty"`
Expiry *NumericDate `json:"exp,omitempty"`
NotBefore *NumericDate `json:"nbf,omitempty"`
IssuedAt *NumericDate `json:"iat,omitempty"`
ID string `json:"jti,omitempty"`
}

// NumericDate represents date and time as the number of seconds since the
Expand All @@ -41,16 +41,17 @@ type Claims struct {
type NumericDate int64

// NewNumericDate constructs NumericDate from time.Time value.
func NewNumericDate(t time.Time) NumericDate {
func NewNumericDate(t time.Time) *NumericDate {
if t.IsZero() {
return NumericDate(0)
return nil
}

// While RFC 7519 technically states that NumericDate values may be
// non-integer values, we don't bother serializing timestamps in
// claims with sub-second accurancy and just round to the nearest
// second instead. Not convined sub-second accuracy is useful here.
return NumericDate(t.Unix())
out := NumericDate(t.Unix())
return &out
}

// MarshalJSON serializes the given NumericDate into its JSON representation.
Expand All @@ -72,13 +73,11 @@ func (n *NumericDate) UnmarshalJSON(b []byte) error {
}

// Time returns time.Time representation of NumericDate.
func (n NumericDate) Time() time.Time {
if n == NumericDate(0) {
// time.Unix(0,0) != time.Time{} (it should, but it doesn't).
// This is a workaround.
func (n *NumericDate) Time() time.Time {
if n == nil {
return time.Time{}
}
return time.Unix(int64(n), 0)
return time.Unix(int64(*n), 0)
}

// Audience represents the recipents that the token is intended for.
Expand Down
34 changes: 30 additions & 4 deletions jwt/claims_test.go
Expand Up @@ -79,11 +79,37 @@ func TestDecodeClaims(t *testing.T) {
}
}

func TestTime(t *testing.T) {
zeroDate := NumericDate(0)
func TestNumericDate(t *testing.T) {
zeroDate := NewNumericDate(time.Time{})
assert.True(t, time.Time{}.Equal(zeroDate.Time()), "Expected derived time to be time.Time{}")

nonZeroDate := NumericDate(1547232324)
expected := time.Date(2019, 1, 11, 18, 45, 24, 0, time.UTC)
zeroDate2 := (*NumericDate)(nil)
assert.True(t, time.Time{}.Equal(zeroDate2.Time()), "Expected derived time to be time.Time{}")

nonZeroDate := NewNumericDate(time.Unix(0, 0))
expected := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
assert.Truef(t, expected.Equal(nonZeroDate.Time()), "Expected derived time to be %s", expected)
}

func TestEncodeClaimsTimeValues(t *testing.T) {
now := time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)

c := Claims{
NotBefore: NewNumericDate(time.Time{}),
IssuedAt: NewNumericDate(time.Unix(0, 0)),
Expiry: NewNumericDate(now),
}

b, err := json.Marshal(c)
assert.NoError(t, err)

expected := `{"exp":1451606400,"iat":0}`
assert.Equal(t, expected, string(b))

c2 := Claims{}
if err := json.Unmarshal(b, &c2); assert.NoError(t, err) {
assert.True(t, c.NotBefore.Time().Equal(c2.NotBefore.Time()))
assert.True(t, c.IssuedAt.Time().Equal(c2.IssuedAt.Time()))
assert.True(t, c.Expiry.Time().Equal(c2.Expiry.Time()))
}
}

0 comments on commit 985395e

Please sign in to comment.