claims.go raw

   1  package jwt
   2  
   3  import (
   4  	"crypto/subtle"
   5  	"fmt"
   6  	"time"
   7  )
   8  
   9  // Claims must just have a Valid method that determines
  10  // if the token is invalid for any supported reason
  11  type Claims interface {
  12  	Valid() error
  13  }
  14  
  15  // RegisteredClaims are a structured version of the JWT Claims Set,
  16  // restricted to Registered Claim Names, as referenced at
  17  // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
  18  //
  19  // This type can be used on its own, but then additional private and
  20  // public claims embedded in the JWT will not be parsed. The typical usecase
  21  // therefore is to embedded this in a user-defined claim type.
  22  //
  23  // See examples for how to use this with your own claim types.
  24  type RegisteredClaims struct {
  25  	// the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
  26  	Issuer string `json:"iss,omitempty"`
  27  
  28  	// the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
  29  	Subject string `json:"sub,omitempty"`
  30  
  31  	// the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
  32  	Audience ClaimStrings `json:"aud,omitempty"`
  33  
  34  	// the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
  35  	ExpiresAt *NumericDate `json:"exp,omitempty"`
  36  
  37  	// the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
  38  	NotBefore *NumericDate `json:"nbf,omitempty"`
  39  
  40  	// the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
  41  	IssuedAt *NumericDate `json:"iat,omitempty"`
  42  
  43  	// the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
  44  	ID string `json:"jti,omitempty"`
  45  }
  46  
  47  // Valid validates time based claims "exp, iat, nbf".
  48  // There is no accounting for clock skew.
  49  // As well, if any of the above claims are not in the token, it will still
  50  // be considered a valid claim.
  51  func (c RegisteredClaims) Valid() error {
  52  	vErr := new(ValidationError)
  53  	now := TimeFunc()
  54  
  55  	// The claims below are optional, by default, so if they are set to the
  56  	// default value in Go, let's not fail the verification for them.
  57  	if !c.VerifyExpiresAt(now, false) {
  58  		delta := now.Sub(c.ExpiresAt.Time)
  59  		vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
  60  		vErr.Errors |= ValidationErrorExpired
  61  	}
  62  
  63  	if !c.VerifyIssuedAt(now, false) {
  64  		vErr.Inner = ErrTokenUsedBeforeIssued
  65  		vErr.Errors |= ValidationErrorIssuedAt
  66  	}
  67  
  68  	if !c.VerifyNotBefore(now, false) {
  69  		vErr.Inner = ErrTokenNotValidYet
  70  		vErr.Errors |= ValidationErrorNotValidYet
  71  	}
  72  
  73  	if vErr.valid() {
  74  		return nil
  75  	}
  76  
  77  	return vErr
  78  }
  79  
  80  // VerifyAudience compares the aud claim against cmp.
  81  // If required is false, this method will return true if the value matches or is unset
  82  func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool {
  83  	return verifyAud(c.Audience, cmp, req)
  84  }
  85  
  86  // VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
  87  // If req is false, it will return true, if exp is unset.
  88  func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool) bool {
  89  	if c.ExpiresAt == nil {
  90  		return verifyExp(nil, cmp, req)
  91  	}
  92  
  93  	return verifyExp(&c.ExpiresAt.Time, cmp, req)
  94  }
  95  
  96  // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
  97  // If req is false, it will return true, if iat is unset.
  98  func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
  99  	if c.IssuedAt == nil {
 100  		return verifyIat(nil, cmp, req)
 101  	}
 102  
 103  	return verifyIat(&c.IssuedAt.Time, cmp, req)
 104  }
 105  
 106  // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
 107  // If req is false, it will return true, if nbf is unset.
 108  func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
 109  	if c.NotBefore == nil {
 110  		return verifyNbf(nil, cmp, req)
 111  	}
 112  
 113  	return verifyNbf(&c.NotBefore.Time, cmp, req)
 114  }
 115  
 116  // VerifyIssuer compares the iss claim against cmp.
 117  // If required is false, this method will return true if the value matches or is unset
 118  func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
 119  	return verifyIss(c.Issuer, cmp, req)
 120  }
 121  
 122  // StandardClaims are a structured version of the JWT Claims Set, as referenced at
 123  // https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the
 124  // specification exactly, since they were based on an earlier draft of the
 125  // specification and not updated. The main difference is that they only
 126  // support integer-based date fields and singular audiences. This might lead to
 127  // incompatibilities with other JWT implementations. The use of this is discouraged, instead
 128  // the newer RegisteredClaims struct should be used.
 129  //
 130  // Deprecated: Use RegisteredClaims instead for a forward-compatible way to access registered claims in a struct.
 131  type StandardClaims struct {
 132  	Audience  string `json:"aud,omitempty"`
 133  	ExpiresAt int64  `json:"exp,omitempty"`
 134  	Id        string `json:"jti,omitempty"`
 135  	IssuedAt  int64  `json:"iat,omitempty"`
 136  	Issuer    string `json:"iss,omitempty"`
 137  	NotBefore int64  `json:"nbf,omitempty"`
 138  	Subject   string `json:"sub,omitempty"`
 139  }
 140  
 141  // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew.
 142  // As well, if any of the above claims are not in the token, it will still
 143  // be considered a valid claim.
 144  func (c StandardClaims) Valid() error {
 145  	vErr := new(ValidationError)
 146  	now := TimeFunc().Unix()
 147  
 148  	// The claims below are optional, by default, so if they are set to the
 149  	// default value in Go, let's not fail the verification for them.
 150  	if !c.VerifyExpiresAt(now, false) {
 151  		delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
 152  		vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
 153  		vErr.Errors |= ValidationErrorExpired
 154  	}
 155  
 156  	if !c.VerifyIssuedAt(now, false) {
 157  		vErr.Inner = ErrTokenUsedBeforeIssued
 158  		vErr.Errors |= ValidationErrorIssuedAt
 159  	}
 160  
 161  	if !c.VerifyNotBefore(now, false) {
 162  		vErr.Inner = ErrTokenNotValidYet
 163  		vErr.Errors |= ValidationErrorNotValidYet
 164  	}
 165  
 166  	if vErr.valid() {
 167  		return nil
 168  	}
 169  
 170  	return vErr
 171  }
 172  
 173  // VerifyAudience compares the aud claim against cmp.
 174  // If required is false, this method will return true if the value matches or is unset
 175  func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
 176  	return verifyAud([]string{c.Audience}, cmp, req)
 177  }
 178  
 179  // VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
 180  // If req is false, it will return true, if exp is unset.
 181  func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
 182  	if c.ExpiresAt == 0 {
 183  		return verifyExp(nil, time.Unix(cmp, 0), req)
 184  	}
 185  
 186  	t := time.Unix(c.ExpiresAt, 0)
 187  	return verifyExp(&t, time.Unix(cmp, 0), req)
 188  }
 189  
 190  // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
 191  // If req is false, it will return true, if iat is unset.
 192  func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
 193  	if c.IssuedAt == 0 {
 194  		return verifyIat(nil, time.Unix(cmp, 0), req)
 195  	}
 196  
 197  	t := time.Unix(c.IssuedAt, 0)
 198  	return verifyIat(&t, time.Unix(cmp, 0), req)
 199  }
 200  
 201  // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
 202  // If req is false, it will return true, if nbf is unset.
 203  func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
 204  	if c.NotBefore == 0 {
 205  		return verifyNbf(nil, time.Unix(cmp, 0), req)
 206  	}
 207  
 208  	t := time.Unix(c.NotBefore, 0)
 209  	return verifyNbf(&t, time.Unix(cmp, 0), req)
 210  }
 211  
 212  // VerifyIssuer compares the iss claim against cmp.
 213  // If required is false, this method will return true if the value matches or is unset
 214  func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
 215  	return verifyIss(c.Issuer, cmp, req)
 216  }
 217  
 218  // ----- helpers
 219  
 220  func verifyAud(aud []string, cmp string, required bool) bool {
 221  	if len(aud) == 0 {
 222  		return !required
 223  	}
 224  	// use a var here to keep constant time compare when looping over a number of claims
 225  	result := false
 226  
 227  	var stringClaims string
 228  	for _, a := range aud {
 229  		if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
 230  			result = true
 231  		}
 232  		stringClaims = stringClaims + a
 233  	}
 234  
 235  	// case where "" is sent in one or many aud claims
 236  	if len(stringClaims) == 0 {
 237  		return !required
 238  	}
 239  
 240  	return result
 241  }
 242  
 243  func verifyExp(exp *time.Time, now time.Time, required bool) bool {
 244  	if exp == nil {
 245  		return !required
 246  	}
 247  	return now.Before(*exp)
 248  }
 249  
 250  func verifyIat(iat *time.Time, now time.Time, required bool) bool {
 251  	if iat == nil {
 252  		return !required
 253  	}
 254  	return now.After(*iat) || now.Equal(*iat)
 255  }
 256  
 257  func verifyNbf(nbf *time.Time, now time.Time, required bool) bool {
 258  	if nbf == nil {
 259  		return !required
 260  	}
 261  	return now.After(*nbf) || now.Equal(*nbf)
 262  }
 263  
 264  func verifyIss(iss string, cmp string, required bool) bool {
 265  	if iss == "" {
 266  		return !required
 267  	}
 268  	return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0
 269  }
 270