Repository: golang-jwt/jwt (2025-08-01T16:01:48Z)

Issue:
No ParserOption for requiring nbf claim
### Description
Reviewing the current ParserOptions, there are many methods to check optional registered claim values (such as WithExpirationRequired()). 

It would appear that the Not Before value is checked currently, but there is no supported way to "require" the not before value like there is with other registered claims.   

## Current behaviour
Currently, if the nbf claim is not present the default parser will not raise a token validation error. 

> NOTE: The default parser currently DOES return an error if the nbf claim is present and it is invalid (i.e. wrong format, invalid time).

## Expected behaviour
I want to require the nbf claim and return the error jwt.ErrTokenRequiredClaimMissing if it is not present.


Discussion:
Makes sense. We might need to split adding the functionality and providing more tests in your PR though



Patch:
diff --git a/parser_option.go b/parser_option.go
index 43157355..d845865d 100644
--- a/parser_option.go
+++ b/parser_option.go
@@ -66,6 +66,14 @@ func WithExpirationRequired() ParserOption {
 	}
 }
 
+// WithNotBeforeRequired returns the ParserOption to make nbf claim required.
+// By default nbf claim is optional.
+func WithNotBeforeRequired() ParserOption {
+	return func(p *Parser) {
+		p.validator.requireNbf = true
+	}
+}
+
 // WithAudience configures the validator to require any of the specified
 // audiences in the `aud` claim. Validation will fail if the audience is not
 // listed in the token or the `aud` claim is missing.
diff --git a/validator.go b/validator.go
index 92b5c057..c82dfcae 100644
--- a/validator.go
+++ b/validator.go
@@ -44,6 +44,9 @@ type Validator struct {
 	// requireExp specifies whether the exp claim is required
 	requireExp bool
 
+	// requireNbf specifies whether the nbf claim is required
+	requireNbf bool
+
 	// verifyIat specifies whether the iat (Issued At) claim will be verified.
 	// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
 	// only specifies the age of the token, but no validation check is
@@ -111,8 +114,9 @@ func (v *Validator) Validate(claims Claims) error {
 	}
 
 	// We always need to check not-before, but usage of the claim itself is
-	// OPTIONAL.
-	if err = v.verifyNotBefore(claims, now, false); err != nil {
+	// OPTIONAL by default. requireNbf overrides this behavior and makes
+	// the nbf claim mandatory.
+	if err = v.verifyNotBefore(claims, now, v.requireNbf); err != nil {
 		errs = append(errs, err)
 	}
 


Test patch:
diff --git a/validator_test.go b/validator_test.go
index 9fdaafab..1790be1f 100644
--- a/validator_test.go
+++ b/validator_test.go
@@ -262,6 +262,73 @@ func Test_Validator_verifyIssuedAt(t *testing.T) {
 	}
 }
 
+func Test_Validator_requireNotBefore(t *testing.T) {
+	type fields struct {
+		leeway     time.Duration
+		timeFunc   func() time.Time
+		requireNbf bool
+	}
+	type args struct {
+		claims   Claims
+		cmp      time.Time
+		required bool
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr error
+	}{
+		{
+			name:    "good claim without nbf",
+			fields:  fields{requireNbf: false},
+			args:    args{claims: MapClaims{}, required: false},
+			wantErr: nil,
+		},
+		{
+			name:   "good claim with nbf",
+			fields: fields{requireNbf: true},
+			args: args{
+				claims:   RegisteredClaims{NotBefore: NewNumericDate(time.Now().Add(time.Minute * -10))},
+				cmp:      time.Now().Add(10 * time.Minute),
+				required: true,
+			},
+			wantErr: nil,
+		},
+		{
+			name:   "token nbf time is in future",
+			fields: fields{requireNbf: true, timeFunc: time.Now},
+			args: args{
+				claims:   RegisteredClaims{NotBefore: NewNumericDate(time.Now().Add(time.Minute * +10))},
+				cmp:      time.Now().Add(10 * time.Minute),
+				required: true,
+			},
+			wantErr: ErrTokenNotValidYet,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			opts := []ParserOption{
+				WithLeeway(tt.fields.leeway),
+			}
+
+			if tt.fields.requireNbf {
+				opts = append(opts, WithNotBeforeRequired())
+			}
+
+			if tt.fields.timeFunc != nil {
+				opts = append(opts, WithTimeFunc(tt.fields.timeFunc))
+			}
+
+			v := NewValidator(opts...)
+
+			if err := v.verifyNotBefore(tt.args.claims, tt.args.cmp, tt.args.required); (err != nil) && !errors.Is(err, tt.wantErr) {
+				t.Errorf("validator.requireNotBefore() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
 func Test_Validator_verifyAudience(t *testing.T) {
 	type fields struct {
 		expectedAud []string