diff --git a/claims.go b/claims.go index 5032d3f..4430c84 100644 --- a/claims.go +++ b/claims.go @@ -7,66 +7,9 @@ package jwt // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 namely `exp`, // `iat`, `nbf`, `iss` and `aud`. type Claims interface { - GetExpiryAt() *NumericDate + GetExpirationTime() *NumericDate GetIssuedAt() *NumericDate GetNotBefore() *NumericDate GetIssuer() string GetAudience() ClaimStrings } - -// RegisteredClaims are a structured version of the JWT Claims Set, -// restricted to Registered Claim Names, as referenced at -// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 -// -// This type can be used on its own, but then additional private and -// public claims embedded in the JWT will not be parsed. The typical use-case -// therefore is to embedded this in a user-defined claim type. -// -// See examples for how to use this with your own claim types. -type RegisteredClaims struct { - // the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1 - Issuer string `json:"iss,omitempty"` - - // the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2 - Subject string `json:"sub,omitempty"` - - // the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3 - Audience ClaimStrings `json:"aud,omitempty"` - - // the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 - ExpiresAt *NumericDate `json:"exp,omitempty"` - - // the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5 - NotBefore *NumericDate `json:"nbf,omitempty"` - - // the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6 - IssuedAt *NumericDate `json:"iat,omitempty"` - - // the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7 - ID string `json:"jti,omitempty"` -} - -// GetExpiryAt implements the Claims interface. -func (c RegisteredClaims) GetExpiryAt() *NumericDate { - return c.ExpiresAt -} - -// GetNotBefore implements the Claims interface. -func (c RegisteredClaims) GetNotBefore() *NumericDate { - return c.NotBefore -} - -// GetIssuedAt implements the Claims interface. -func (c RegisteredClaims) GetIssuedAt() *NumericDate { - return c.IssuedAt -} - -// GetAudience implements the Claims interface. -func (c RegisteredClaims) GetAudience() ClaimStrings { - return c.Audience -} - -// GetIssuer implements the Claims interface. -func (c RegisteredClaims) GetIssuer() string { - return c.Issuer -} diff --git a/map_claims.go b/map_claims.go index d7b0830..93d36ba 100644 --- a/map_claims.go +++ b/map_claims.go @@ -8,8 +8,8 @@ import ( // This is the default claims type if you don't supply one type MapClaims map[string]interface{} -// GetExpiryAt implements the Claims interface. -func (m MapClaims) GetExpiryAt() *NumericDate { +// GetExpirationTime implements the Claims interface. +func (m MapClaims) GetExpirationTime() *NumericDate { return m.ParseNumericDate("exp") } @@ -33,6 +33,9 @@ func (m MapClaims) GetIssuer() string { return m.ParseString("iss") } +// ParseNumericDate tries to parse a key in the map claims type as a number +// date. This will succeed, if the underlying type is either a [float64] or a +// [json.Number]. Otherwise, nil will be returned. func (m MapClaims) ParseNumericDate(key string) *NumericDate { v, ok := m[key] if !ok { @@ -55,6 +58,8 @@ func (m MapClaims) ParseNumericDate(key string) *NumericDate { return nil } +// ParseClaimsString tries to parse a key in the map claims type as a +// [ClaimsStrings] type, which can either be a string or an array of string. func (m MapClaims) ParseClaimsString(key string) ClaimStrings { var cs []string switch v := m[key].(type) { @@ -75,6 +80,8 @@ func (m MapClaims) ParseClaimsString(key string) ClaimStrings { return cs } +// ParseString tries to parse a key in the map claims type as a +// [string] type. Otherwise, an empty string is returned. func (m MapClaims) ParseString(key string) string { iss, _ := m[key].(string) diff --git a/parser_test.go b/parser_test.go index c23395a..5faeff4 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3,6 +3,7 @@ package jwt_test import ( "crypto" "crypto/rsa" + "encoding/json" "errors" "fmt" "reflect" @@ -55,7 +56,7 @@ var jwtTestData = []struct { parser *jwt.Parser signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose }{ - /*{ + { "basic", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", defaultKeyFunc, @@ -317,7 +318,7 @@ var jwtTestData = []struct { []error{jwt.ErrTokenNotValidYet}, jwt.NewParser(jwt.WithValidator(jwt.NewValidator(jwt.WithLeeway(time.Minute)))), jwt.SigningMethodRS256, - },*/ + }, { "RFC7519 Claims - nbf with 120s skew", "", // autogen diff --git a/registered_claims.go b/registered_claims.go new file mode 100644 index 0000000..0023790 --- /dev/null +++ b/registered_claims.go @@ -0,0 +1,58 @@ +package jwt + +// RegisteredClaims are a structured version of the JWT Claims Set, +// restricted to Registered Claim Names, as referenced at +// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 +// +// This type can be used on its own, but then additional private and +// public claims embedded in the JWT will not be parsed. The typical use-case +// therefore is to embedded this in a user-defined claim type. +// +// See examples for how to use this with your own claim types. +type RegisteredClaims struct { + // the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1 + Issuer string `json:"iss,omitempty"` + + // the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2 + Subject string `json:"sub,omitempty"` + + // the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3 + Audience ClaimStrings `json:"aud,omitempty"` + + // the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4 + ExpiresAt *NumericDate `json:"exp,omitempty"` + + // the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5 + NotBefore *NumericDate `json:"nbf,omitempty"` + + // the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6 + IssuedAt *NumericDate `json:"iat,omitempty"` + + // the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7 + ID string `json:"jti,omitempty"` +} + +// GetExpirationTime implements the Claims interface. +func (c RegisteredClaims) GetExpirationTime() *NumericDate { + return c.ExpiresAt +} + +// GetNotBefore implements the Claims interface. +func (c RegisteredClaims) GetNotBefore() *NumericDate { + return c.NotBefore +} + +// GetIssuedAt implements the Claims interface. +func (c RegisteredClaims) GetIssuedAt() *NumericDate { + return c.IssuedAt +} + +// GetAudience implements the Claims interface. +func (c RegisteredClaims) GetAudience() ClaimStrings { + return c.Audience +} + +// GetIssuer implements the Claims interface. +func (c RegisteredClaims) GetIssuer() string { + return c.Issuer +} diff --git a/validator.go b/validator.go index cac68eb..d85a289 100644 --- a/validator.go +++ b/validator.go @@ -15,7 +15,7 @@ func (v *Validator) Validate(claims Claims) error { now := TimeFunc() if !v.VerifyExpiresAt(claims, now, false) { - exp := claims.GetExpiryAt() + exp := claims.GetExpirationTime() delta := now.Sub(exp.Time) vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta) vErr.Errors |= ValidationErrorExpired @@ -47,7 +47,7 @@ func (v *Validator) VerifyAudience(claims Claims, cmp string, req bool) bool { // VerifyExpiresAt compares the exp claim against cmp (cmp < exp). // If req is false, it will return true, if exp is unset. func (v *Validator) VerifyExpiresAt(claims Claims, cmp time.Time, req bool) bool { - exp := claims.GetExpiryAt() + exp := claims.GetExpirationTime() if exp == nil { return verifyExp(nil, cmp, req, v.leeway) }