...

Source file src/github.com/golang-jwt/jwt/v5/validator.go

Documentation: github.com/golang-jwt/jwt/v5

     1  package jwt
     2  
     3  import (
     4  	"crypto/subtle"
     5  	"fmt"
     6  	"time"
     7  )
     8  
     9  // ClaimsValidator is an interface that can be implemented by custom claims who
    10  // wish to execute any additional claims validation based on
    11  // application-specific logic. The Validate function is then executed in
    12  // addition to the regular claims validation and any error returned is appended
    13  // to the final validation result.
    14  //
    15  //	type MyCustomClaims struct {
    16  //	    Foo string `json:"foo"`
    17  //	    jwt.RegisteredClaims
    18  //	}
    19  //
    20  //	func (m MyCustomClaims) Validate() error {
    21  //	    if m.Foo != "bar" {
    22  //	        return errors.New("must be foobar")
    23  //	    }
    24  //	    return nil
    25  //	}
    26  type ClaimsValidator interface {
    27  	Claims
    28  	Validate() error
    29  }
    30  
    31  // Validator is the core of the new Validation API. It is automatically used by
    32  // a [Parser] during parsing and can be modified with various parser options.
    33  //
    34  // The [NewValidator] function should be used to create an instance of this
    35  // struct.
    36  type Validator struct {
    37  	// leeway is an optional leeway that can be provided to account for clock skew.
    38  	leeway time.Duration
    39  
    40  	// timeFunc is used to supply the current time that is needed for
    41  	// validation. If unspecified, this defaults to time.Now.
    42  	timeFunc func() time.Time
    43  
    44  	// requireExp specifies whether the exp claim is required
    45  	requireExp bool
    46  
    47  	// verifyIat specifies whether the iat (Issued At) claim will be verified.
    48  	// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
    49  	// only specifies the age of the token, but no validation check is
    50  	// necessary. However, if wanted, it can be checked if the iat is
    51  	// unrealistic, i.e., in the future.
    52  	verifyIat bool
    53  
    54  	// expectedAud contains the audience this token expects. Supplying an empty
    55  	// string will disable aud checking.
    56  	expectedAud string
    57  
    58  	// expectedIss contains the issuer this token expects. Supplying an empty
    59  	// string will disable iss checking.
    60  	expectedIss string
    61  
    62  	// expectedSub contains the subject this token expects. Supplying an empty
    63  	// string will disable sub checking.
    64  	expectedSub string
    65  }
    66  
    67  // NewValidator can be used to create a stand-alone validator with the supplied
    68  // options. This validator can then be used to validate already parsed claims.
    69  //
    70  // Note: Under normal circumstances, explicitly creating a validator is not
    71  // needed and can potentially be dangerous; instead functions of the [Parser]
    72  // class should be used.
    73  //
    74  // The [Validator] is only checking the *validity* of the claims, such as its
    75  // expiration time, but it does NOT perform *signature verification* of the
    76  // token.
    77  func NewValidator(opts ...ParserOption) *Validator {
    78  	p := NewParser(opts...)
    79  	return p.validator
    80  }
    81  
    82  // Validate validates the given claims. It will also perform any custom
    83  // validation if claims implements the [ClaimsValidator] interface.
    84  //
    85  // Note: It will NOT perform any *signature verification* on the token that
    86  // contains the claims and expects that the [Claim] was already successfully
    87  // verified.
    88  func (v *Validator) Validate(claims Claims) error {
    89  	var (
    90  		now  time.Time
    91  		errs []error = make([]error, 0, 6)
    92  		err  error
    93  	)
    94  
    95  	// Check, if we have a time func
    96  	if v.timeFunc != nil {
    97  		now = v.timeFunc()
    98  	} else {
    99  		now = time.Now()
   100  	}
   101  
   102  	// We always need to check the expiration time, but usage of the claim
   103  	// itself is OPTIONAL by default. requireExp overrides this behavior
   104  	// and makes the exp claim mandatory.
   105  	if err = v.verifyExpiresAt(claims, now, v.requireExp); err != nil {
   106  		errs = append(errs, err)
   107  	}
   108  
   109  	// We always need to check not-before, but usage of the claim itself is
   110  	// OPTIONAL.
   111  	if err = v.verifyNotBefore(claims, now, false); err != nil {
   112  		errs = append(errs, err)
   113  	}
   114  
   115  	// Check issued-at if the option is enabled
   116  	if v.verifyIat {
   117  		if err = v.verifyIssuedAt(claims, now, false); err != nil {
   118  			errs = append(errs, err)
   119  		}
   120  	}
   121  
   122  	// If we have an expected audience, we also require the audience claim
   123  	if v.expectedAud != "" {
   124  		if err = v.verifyAudience(claims, v.expectedAud, true); err != nil {
   125  			errs = append(errs, err)
   126  		}
   127  	}
   128  
   129  	// If we have an expected issuer, we also require the issuer claim
   130  	if v.expectedIss != "" {
   131  		if err = v.verifyIssuer(claims, v.expectedIss, true); err != nil {
   132  			errs = append(errs, err)
   133  		}
   134  	}
   135  
   136  	// If we have an expected subject, we also require the subject claim
   137  	if v.expectedSub != "" {
   138  		if err = v.verifySubject(claims, v.expectedSub, true); err != nil {
   139  			errs = append(errs, err)
   140  		}
   141  	}
   142  
   143  	// Finally, we want to give the claim itself some possibility to do some
   144  	// additional custom validation based on a custom Validate function.
   145  	cvt, ok := claims.(ClaimsValidator)
   146  	if ok {
   147  		if err := cvt.Validate(); err != nil {
   148  			errs = append(errs, err)
   149  		}
   150  	}
   151  
   152  	if len(errs) == 0 {
   153  		return nil
   154  	}
   155  
   156  	return joinErrors(errs...)
   157  }
   158  
   159  // verifyExpiresAt compares the exp claim in claims against cmp. This function
   160  // will succeed if cmp < exp. Additional leeway is taken into account.
   161  //
   162  // If exp is not set, it will succeed if the claim is not required,
   163  // otherwise ErrTokenRequiredClaimMissing will be returned.
   164  //
   165  // Additionally, if any error occurs while retrieving the claim, e.g., when its
   166  // the wrong type, an ErrTokenUnverifiable error will be returned.
   167  func (v *Validator) verifyExpiresAt(claims Claims, cmp time.Time, required bool) error {
   168  	exp, err := claims.GetExpirationTime()
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	if exp == nil {
   174  		return errorIfRequired(required, "exp")
   175  	}
   176  
   177  	return errorIfFalse(cmp.Before((exp.Time).Add(+v.leeway)), ErrTokenExpired)
   178  }
   179  
   180  // verifyIssuedAt compares the iat claim in claims against cmp. This function
   181  // will succeed if cmp >= iat. Additional leeway is taken into account.
   182  //
   183  // If iat is not set, it will succeed if the claim is not required,
   184  // otherwise ErrTokenRequiredClaimMissing will be returned.
   185  //
   186  // Additionally, if any error occurs while retrieving the claim, e.g., when its
   187  // the wrong type, an ErrTokenUnverifiable error will be returned.
   188  func (v *Validator) verifyIssuedAt(claims Claims, cmp time.Time, required bool) error {
   189  	iat, err := claims.GetIssuedAt()
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	if iat == nil {
   195  		return errorIfRequired(required, "iat")
   196  	}
   197  
   198  	return errorIfFalse(!cmp.Before(iat.Add(-v.leeway)), ErrTokenUsedBeforeIssued)
   199  }
   200  
   201  // verifyNotBefore compares the nbf claim in claims against cmp. This function
   202  // will return true if cmp >= nbf. Additional leeway is taken into account.
   203  //
   204  // If nbf is not set, it will succeed if the claim is not required,
   205  // otherwise ErrTokenRequiredClaimMissing will be returned.
   206  //
   207  // Additionally, if any error occurs while retrieving the claim, e.g., when its
   208  // the wrong type, an ErrTokenUnverifiable error will be returned.
   209  func (v *Validator) verifyNotBefore(claims Claims, cmp time.Time, required bool) error {
   210  	nbf, err := claims.GetNotBefore()
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	if nbf == nil {
   216  		return errorIfRequired(required, "nbf")
   217  	}
   218  
   219  	return errorIfFalse(!cmp.Before(nbf.Add(-v.leeway)), ErrTokenNotValidYet)
   220  }
   221  
   222  // verifyAudience compares the aud claim against cmp.
   223  //
   224  // If aud is not set or an empty list, it will succeed if the claim is not required,
   225  // otherwise ErrTokenRequiredClaimMissing will be returned.
   226  //
   227  // Additionally, if any error occurs while retrieving the claim, e.g., when its
   228  // the wrong type, an ErrTokenUnverifiable error will be returned.
   229  func (v *Validator) verifyAudience(claims Claims, cmp string, required bool) error {
   230  	aud, err := claims.GetAudience()
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	if len(aud) == 0 {
   236  		return errorIfRequired(required, "aud")
   237  	}
   238  
   239  	// use a var here to keep constant time compare when looping over a number of claims
   240  	result := false
   241  
   242  	var stringClaims string
   243  	for _, a := range aud {
   244  		if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
   245  			result = true
   246  		}
   247  		stringClaims = stringClaims + a
   248  	}
   249  
   250  	// case where "" is sent in one or many aud claims
   251  	if stringClaims == "" {
   252  		return errorIfRequired(required, "aud")
   253  	}
   254  
   255  	return errorIfFalse(result, ErrTokenInvalidAudience)
   256  }
   257  
   258  // verifyIssuer compares the iss claim in claims against cmp.
   259  //
   260  // If iss is not set, it will succeed if the claim is not required,
   261  // otherwise ErrTokenRequiredClaimMissing will be returned.
   262  //
   263  // Additionally, if any error occurs while retrieving the claim, e.g., when its
   264  // the wrong type, an ErrTokenUnverifiable error will be returned.
   265  func (v *Validator) verifyIssuer(claims Claims, cmp string, required bool) error {
   266  	iss, err := claims.GetIssuer()
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	if iss == "" {
   272  		return errorIfRequired(required, "iss")
   273  	}
   274  
   275  	return errorIfFalse(iss == cmp, ErrTokenInvalidIssuer)
   276  }
   277  
   278  // verifySubject compares the sub claim against cmp.
   279  //
   280  // If sub is not set, it will succeed if the claim is not required,
   281  // otherwise ErrTokenRequiredClaimMissing will be returned.
   282  //
   283  // Additionally, if any error occurs while retrieving the claim, e.g., when its
   284  // the wrong type, an ErrTokenUnverifiable error will be returned.
   285  func (v *Validator) verifySubject(claims Claims, cmp string, required bool) error {
   286  	sub, err := claims.GetSubject()
   287  	if err != nil {
   288  		return err
   289  	}
   290  
   291  	if sub == "" {
   292  		return errorIfRequired(required, "sub")
   293  	}
   294  
   295  	return errorIfFalse(sub == cmp, ErrTokenInvalidSubject)
   296  }
   297  
   298  // errorIfFalse returns the error specified in err, if the value is true.
   299  // Otherwise, nil is returned.
   300  func errorIfFalse(value bool, err error) error {
   301  	if value {
   302  		return nil
   303  	} else {
   304  		return err
   305  	}
   306  }
   307  
   308  // errorIfRequired returns an ErrTokenRequiredClaimMissing error if required is
   309  // true. Otherwise, nil is returned.
   310  func errorIfRequired(required bool, claim string) error {
   311  	if required {
   312  		return newError(fmt.Sprintf("%s claim is required", claim), ErrTokenRequiredClaimMissing)
   313  	} else {
   314  		return nil
   315  	}
   316  }
   317  

View as plain text