Skip to content

Commit e8e5b83

Browse files
authored
mapclaims: stop treating exp=0 as a missing claim (#509)
* mapclaims: stop treating exp=0 as a missing claim MapClaims.parseNumericDate short-circuited and returned nil whenever a float64 value happened to be 0. That made the very-much-set exp claim "exp": 0 invisible to verifyExpiresAt, so the token was treated as valid instead of expired. The json.Number branch doesn't have the same carve-out, so parsing the exact same token via WithJSONNumber() correctly came back as expired. Drop the special case so both branches agree and an exp of 0 means "this token expired at the unix epoch", which is what the JWT spec calls for. Fixes #496. Signed-off-by: Charlie Tonneslan <cst0520@gmail.com> * mapclaims: test that string exp comes back as ErrInvalidType Pins down the corner case raised in review: empty-string exp must surface as ErrInvalidType rather than slipping into the float64 branch and looking like a 0 (i.e. epoch) value. Signed-off-by: Charlie Tonneslan <cst0520@gmail.com> --------- Signed-off-by: Charlie Tonneslan <cst0520@gmail.com>
1 parent 9a70137 commit e8e5b83

2 files changed

Lines changed: 44 additions & 4 deletions

File tree

map_claims.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ func (m MapClaims) parseNumericDate(key string) (*NumericDate, error) {
5050

5151
switch exp := v.(type) {
5252
case float64:
53-
if exp == 0 {
54-
return nil, nil
55-
}
56-
5753
return newNumericDateFromSeconds(exp), nil
5854
case json.Number:
5955
v, _ := exp.Float64()

map_claims_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package jwt
22

33
import (
4+
"encoding/json"
5+
"errors"
46
"testing"
57
"time"
68
)
@@ -192,3 +194,45 @@ func TestMapClaims_parseString(t *testing.T) {
192194
})
193195
}
194196
}
197+
198+
// Regression for #496: a token whose exp claim is the literal 0 (i.e.
199+
// 1970-01-01) used to be treated as valid because parseNumericDate
200+
// special-cased a zero float64 as "claim not present". The json.Number
201+
// branch had no such carve-out, so the same token parsed via
202+
// WithJSONNumber() correctly came back expired.
203+
func TestMapClaims_GetExpirationTime_ZeroIsExpired(t *testing.T) {
204+
for name, claims := range map[string]MapClaims{
205+
"float64": {"exp": float64(0)},
206+
"json.Number": {"exp": json.Number("0")},
207+
} {
208+
t.Run(name, func(t *testing.T) {
209+
err := NewValidator().Validate(claims)
210+
if err == nil {
211+
t.Fatalf("expected an error for exp=0, got nil")
212+
}
213+
if !errors.Is(err, ErrTokenExpired) {
214+
t.Fatalf("expected ErrTokenExpired, got %v", err)
215+
}
216+
})
217+
}
218+
}
219+
220+
// A string exp must come back as ErrInvalidType, not as a stealth
221+
// "claim not present" via the old float64==0 shortcut. Empty string is
222+
// the case worth pinning down explicitly.
223+
func TestMapClaims_GetExpirationTime_StringIsInvalidType(t *testing.T) {
224+
for name, claims := range map[string]MapClaims{
225+
"empty string": {"exp": ""},
226+
"non-empty": {"exp": "foo"},
227+
} {
228+
t.Run(name, func(t *testing.T) {
229+
_, err := claims.GetExpirationTime()
230+
if err == nil {
231+
t.Fatalf("expected an error, got nil")
232+
}
233+
if !errors.Is(err, ErrInvalidType) {
234+
t.Fatalf("expected ErrInvalidType, got %v", err)
235+
}
236+
})
237+
}
238+
}

0 commit comments

Comments
 (0)