...

Source file src/github.com/lestrrat-go/jwx/jwt/validate_test.go

Documentation: github.com/lestrrat-go/jwx/jwt

     1  package jwt_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/lestrrat-go/jwx/internal/json"
    10  	"github.com/lestrrat-go/jwx/jwt"
    11  	"github.com/stretchr/testify/assert"
    12  )
    13  
    14  func TestGHIssue10(t *testing.T) {
    15  	t.Parallel()
    16  
    17  	// Simple string claims
    18  	testcases := []struct {
    19  		ClaimName  string
    20  		ClaimValue string
    21  		OptionFunc func(string) jwt.ValidateOption
    22  		BuildFunc  func(v string) (jwt.Token, error)
    23  	}{
    24  		{
    25  			ClaimName:  jwt.IssuerKey,
    26  			ClaimValue: `github.com/lestrrat-go/jwx`,
    27  			OptionFunc: jwt.WithIssuer,
    28  			BuildFunc: func(v string) (jwt.Token, error) {
    29  				return jwt.NewBuilder().
    30  					Issuer(v).
    31  					Build()
    32  			},
    33  		},
    34  		{
    35  			ClaimName:  jwt.JwtIDKey,
    36  			ClaimValue: `my-sepcial-key`,
    37  			OptionFunc: jwt.WithJwtID,
    38  			BuildFunc: func(v string) (jwt.Token, error) {
    39  				return jwt.NewBuilder().
    40  					JwtID(v).
    41  					Build()
    42  			},
    43  		},
    44  		{
    45  			ClaimName:  jwt.SubjectKey,
    46  			ClaimValue: `very important subject`,
    47  			OptionFunc: jwt.WithSubject,
    48  			BuildFunc: func(v string) (jwt.Token, error) {
    49  				return jwt.NewBuilder().
    50  					Subject(v).
    51  					Build()
    52  			},
    53  		},
    54  	}
    55  	for _, tc := range testcases {
    56  		tc := tc
    57  		t.Run(tc.ClaimName, func(t *testing.T) {
    58  			t.Parallel()
    59  			t1, err := tc.BuildFunc(tc.ClaimValue)
    60  			if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
    61  				return
    62  			}
    63  
    64  			// This should succeed, because validation option (tc.OptionFunc)
    65  			// is not provided in the optional parameters
    66  			if !assert.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") {
    67  				return
    68  			}
    69  
    70  			// This should succeed, because the option is provided with same value
    71  			if !assert.NoError(t, jwt.Validate(t1, tc.OptionFunc(tc.ClaimValue)), "t1.Validate should succeed") {
    72  				return
    73  			}
    74  
    75  			if !assert.Error(t, jwt.Validate(t1, jwt.WithIssuer("poop")), "t1.Validate should fail") {
    76  				return
    77  			}
    78  		})
    79  	}
    80  	t.Run(jwt.IssuedAtKey, func(t *testing.T) {
    81  		t.Parallel()
    82  		t1 := jwt.New()
    83  		t1.Set(jwt.IssuedAtKey, time.Now().Add(365*24*time.Second))
    84  
    85  		t.Run(`iat too far in the past`, func(t *testing.T) {
    86  			err := jwt.Validate(t1)
    87  			if !assert.Error(t, err, `jwt.Validate should fail`) {
    88  				return
    89  			}
    90  
    91  			if !assert.True(t, errors.Is(err, jwt.ErrInvalidIssuedAt()), `error should be jwt.ErrInvalidIssuedAt`) {
    92  				return
    93  			}
    94  
    95  			if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) {
    96  				return
    97  			}
    98  
    99  			if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
   100  				return
   101  			}
   102  		})
   103  	})
   104  	t.Run(jwt.AudienceKey, func(t *testing.T) {
   105  		t.Parallel()
   106  		t1, err := jwt.NewBuilder().
   107  			Claim(jwt.AudienceKey, []string{"foo", "bar", "baz"}).
   108  			Build()
   109  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   110  			return
   111  		}
   112  
   113  		// This should succeed, because WithAudience is not provided in the
   114  		// optional parameters
   115  		t.Run("`aud` check disabled", func(t *testing.T) {
   116  			t.Parallel()
   117  			if !assert.NoError(t, jwt.Validate(t1), `jwt.Validate should succeed`) {
   118  				return
   119  			}
   120  		})
   121  
   122  		// This should succeed, because WithAudience is provided, and its
   123  		// value matches one of the audience values
   124  		t.Run("`aud` contains `baz`", func(t *testing.T) {
   125  			t.Parallel()
   126  			if !assert.NoError(t, jwt.Validate(t1, jwt.WithAudience("baz")), "jwt.Validate should succeed") {
   127  				return
   128  			}
   129  		})
   130  
   131  		t.Run("check `aud` contains `poop`", func(t *testing.T) {
   132  			t.Parallel()
   133  			err := jwt.Validate(t1, jwt.WithAudience("poop"))
   134  			if !assert.Error(t, err, "token.Validate should fail") {
   135  				return
   136  			}
   137  			if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) {
   138  				return
   139  			}
   140  			if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
   141  				return
   142  			}
   143  		})
   144  	})
   145  	t.Run(jwt.SubjectKey, func(t *testing.T) {
   146  		t.Parallel()
   147  		t1, err := jwt.NewBuilder().
   148  			Claim(jwt.SubjectKey, "github.com/lestrrat-go/jwx").
   149  			Build()
   150  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   151  			return
   152  		}
   153  
   154  		// This should succeed, because WithSubject is not provided in the
   155  		// optional parameters
   156  		if !assert.NoError(t, jwt.Validate(t1), "token.Validate should succeed") {
   157  			return
   158  		}
   159  
   160  		// This should succeed, because WithSubject is provided with same value
   161  		if !assert.NoError(t, jwt.Validate(t1, jwt.WithSubject(t1.Subject())), "token.Validate should succeed") {
   162  			return
   163  		}
   164  
   165  		if !assert.Error(t, jwt.Validate(t1, jwt.WithSubject("poop")), "token.Validate should fail") {
   166  			return
   167  		}
   168  	})
   169  	t.Run(jwt.NotBeforeKey, func(t *testing.T) {
   170  		t.Parallel()
   171  
   172  		// NotBefore is set to future date
   173  		tm := time.Now().Add(72 * time.Hour)
   174  
   175  		t1, err := jwt.NewBuilder().
   176  			Claim(jwt.NotBeforeKey, tm).
   177  			Build()
   178  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   179  			return
   180  		}
   181  
   182  		testcases := []struct {
   183  			Name    string
   184  			Options []jwt.ValidateOption
   185  			Error   bool
   186  		}{
   187  			{ // This should fail, because nbf is the future
   188  				Name:  `'nbf' is less than current time`,
   189  				Error: true,
   190  			},
   191  			{ // This should succeed, because we have given reaaaaaaly big skew
   192  				Name: `skew is large enough`,
   193  				Options: []jwt.ValidateOption{
   194  					jwt.WithAcceptableSkew(73 * time.Hour),
   195  				},
   196  			},
   197  			{ // This should succeed, because we have given a time
   198  				// that is well enough into the future
   199  				Name: `clock is set to after time in nbf`,
   200  				Options: []jwt.ValidateOption{
   201  					jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm.Add(time.Hour) })),
   202  				},
   203  			},
   204  			{ // This should succeed, the time == NotBefore time
   205  				// Note, this may fail if you are return a monotonic clock
   206  				Name: `clock is set to the same time as nbf`,
   207  				Options: []jwt.ValidateOption{
   208  					jwt.WithClock(jwt.ClockFunc(func() time.Time { return tm })),
   209  				},
   210  			},
   211  		}
   212  		for _, tc := range testcases {
   213  			tc := tc
   214  			t.Run(tc.Name, func(t *testing.T) {
   215  				if tc.Error {
   216  					err := jwt.Validate(t1, tc.Options...)
   217  					if !assert.Error(t, err, "token.Validate should fail") {
   218  						return
   219  					}
   220  					if !assert.True(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be ErrTokenNotYetValid`) {
   221  						return
   222  					}
   223  					if !assert.False(t, errors.Is(err, jwt.ErrTokenExpired()), `error should not be ErrTokenExpierd`) {
   224  						return
   225  					}
   226  					if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
   227  						return
   228  					}
   229  				} else {
   230  					if !assert.NoError(t, jwt.Validate(t1, tc.Options...), "token.Validate should succeed") {
   231  						return
   232  					}
   233  				}
   234  			})
   235  		}
   236  	})
   237  	t.Run(jwt.ExpirationKey, func(t *testing.T) {
   238  		t.Parallel()
   239  
   240  		tm := time.Now()
   241  		t1, err := jwt.NewBuilder().
   242  			// issuedat = 1 Hr before current time
   243  			Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)).
   244  			// valid for 2 minutes only from IssuedAt
   245  			Claim(jwt.ExpirationKey, tm.Add(-58*time.Minute)).
   246  			Build()
   247  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   248  			return
   249  		}
   250  
   251  		// This should fail, because exp is set in the past
   252  		t.Run("exp set in the past", func(t *testing.T) {
   253  			t.Parallel()
   254  			err := jwt.Validate(t1)
   255  			if !assert.Error(t, err, "token.Validate should fail") {
   256  				return
   257  			}
   258  			if !assert.True(t, errors.Is(err, jwt.ErrTokenExpired()), `error should be ErrTokenExpired`) {
   259  				return
   260  			}
   261  			if !assert.False(t, errors.Is(err, jwt.ErrTokenNotYetValid()), `error should be not ErrNotYetValid`) {
   262  				return
   263  			}
   264  			if !assert.True(t, jwt.IsValidationError(err), `error should be a validation error`) {
   265  				return
   266  			}
   267  		})
   268  		// This should succeed, because we have given big skew
   269  		// that is well enough to get us accepted
   270  		t.Run("exp is set in the past, but acceptable skew is large", func(t *testing.T) {
   271  			t.Parallel()
   272  			if !assert.NoError(t, jwt.Validate(t1, jwt.WithAcceptableSkew(time.Hour)), "token.Validate should succeed (1)") {
   273  				return
   274  			}
   275  		})
   276  
   277  		// This should succeed, because we have given a time
   278  		// that is well enough into the past
   279  		t.Run("exp is set in the past, but clock is also set in the past", func(t *testing.T) {
   280  			t.Parallel()
   281  			clock := jwt.ClockFunc(func() time.Time {
   282  				return tm.Add(-59 * time.Minute)
   283  			})
   284  			if !assert.NoError(t, jwt.Validate(t1, jwt.WithClock(clock)), "token.Validate should succeed (2)") {
   285  				return
   286  			}
   287  		})
   288  	})
   289  	t.Run("Unix zero times", func(t *testing.T) {
   290  		t.Parallel()
   291  		tm := time.Unix(0, 0)
   292  		t1, err := jwt.NewBuilder().
   293  			Claim(jwt.NotBeforeKey, tm).
   294  			Claim(jwt.IssuedAtKey, tm).
   295  			Claim(jwt.ExpirationKey, tm).
   296  			Build()
   297  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   298  			return
   299  		}
   300  
   301  		// This should pass because the unix zero times should be ignored
   302  		if assert.NoError(t, jwt.Validate(t1), "token.Validate should pass") {
   303  			return
   304  		}
   305  	})
   306  	t.Run("Go zero times", func(t *testing.T) {
   307  		t.Parallel()
   308  		tm := time.Time{}
   309  		t1, err := jwt.NewBuilder().
   310  			Claim(jwt.NotBeforeKey, tm).
   311  			Claim(jwt.IssuedAtKey, tm).
   312  			Claim(jwt.ExpirationKey, tm).
   313  			Build()
   314  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   315  			return
   316  		}
   317  
   318  		// This should pass because the go zero times should be ignored
   319  		if assert.NoError(t, jwt.Validate(t1), "token.Validate should pass") {
   320  			return
   321  		}
   322  	})
   323  	t.Run("Parse and validate", func(t *testing.T) {
   324  		t.Parallel()
   325  		tm := time.Now()
   326  		t1, err := jwt.NewBuilder().
   327  			// issuedat = 1 Hr before current time
   328  			Claim(jwt.IssuedAtKey, tm.Add(-1*time.Hour)).
   329  			// valid for 2 minutes only from IssuedAt
   330  			Claim(jwt.ExpirationKey, tm.Add(-58*time.Minute)).
   331  			Build()
   332  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   333  			return
   334  		}
   335  
   336  		buf, err := json.Marshal(t1)
   337  		if !assert.NoError(t, err, `json.Marshal should succeed`) {
   338  			return
   339  		}
   340  
   341  		_, err = jwt.Parse(buf, jwt.WithValidate(true))
   342  		// This should fail, because exp is set in the past
   343  		if !assert.Error(t, err, "jwt.Parse should fail") {
   344  			return
   345  		}
   346  
   347  		_, err = jwt.Parse(buf, jwt.WithValidate(true), jwt.WithAcceptableSkew(time.Hour))
   348  		// This should succeed, because we have given big skew
   349  		// that is well enough to get us accepted
   350  		if !assert.NoError(t, err, "jwt.Parse should succeed (1)") {
   351  			return
   352  		}
   353  
   354  		// This should succeed, because we have given a time
   355  		// that is well enough into the past
   356  		clock := jwt.ClockFunc(func() time.Time {
   357  			return tm.Add(-59 * time.Minute)
   358  		})
   359  		_, err = jwt.Parse(buf, jwt.WithValidate(true), jwt.WithClock(clock))
   360  		if !assert.NoError(t, err, "jwt.Parse should succeed (2)") {
   361  			return
   362  		}
   363  	})
   364  	t.Run("any claim value", func(t *testing.T) {
   365  		t.Parallel()
   366  		t1, err := jwt.NewBuilder().
   367  			Claim("email", "email@example.com").
   368  			Build()
   369  		if !assert.NoError(t, err, `jwt.NewBuilder should succeed`) {
   370  			return
   371  		}
   372  
   373  		// This should succeed, because WithClaimValue("email", "xxx") is not provided in the
   374  		// optional parameters
   375  		if !assert.NoError(t, jwt.Validate(t1), "t1.Validate should succeed") {
   376  			return
   377  		}
   378  
   379  		// This should succeed, because WithClaimValue is provided with same value
   380  		if !assert.NoError(t, jwt.Validate(t1, jwt.WithClaimValue("email", "email@example.com")), "t1.Validate should succeed") {
   381  			return
   382  		}
   383  
   384  		if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("email", "poop")), "t1.Validate should fail") {
   385  			return
   386  		}
   387  		if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "email@example.com")), "t1.Validate should fail") {
   388  			return
   389  		}
   390  		if !assert.Error(t, jwt.Validate(t1, jwt.WithClaimValue("xxxx", "")), "t1.Validate should fail") {
   391  			return
   392  		}
   393  	})
   394  }
   395  
   396  func TestClaimValidator(t *testing.T) {
   397  	t.Parallel()
   398  	const myClaim = "my-claim"
   399  	err0 := errors.New(myClaim + " does not exist")
   400  	v := jwt.ValidatorFunc(func(_ context.Context, tok jwt.Token) error {
   401  		_, ok := tok.Get(myClaim)
   402  		if !ok {
   403  			return err0
   404  		}
   405  		return nil
   406  	})
   407  
   408  	testcases := []struct {
   409  		Name      string
   410  		MakeToken func() jwt.Token
   411  		Error     error
   412  	}{
   413  		{
   414  			Name: "Successful validation",
   415  			MakeToken: func() jwt.Token {
   416  				t1 := jwt.New()
   417  				_ = t1.Set(myClaim, map[string]interface{}{"k": "v"})
   418  				return t1
   419  			},
   420  		},
   421  		{
   422  			Name: "Target claim does not exist",
   423  			MakeToken: func() jwt.Token {
   424  				t1 := jwt.New()
   425  				_ = t1.Set("other-claim", map[string]interface{}{"k": "v"})
   426  				return t1
   427  			},
   428  			Error: err0,
   429  		},
   430  	}
   431  	for _, tc := range testcases {
   432  		tc := tc
   433  		t.Run(tc.Name, func(t *testing.T) {
   434  			t.Parallel()
   435  			t1 := tc.MakeToken()
   436  			if err := tc.Error; err != nil {
   437  				if !assert.ErrorIs(t, jwt.Validate(t1, jwt.WithValidator(v)), err) {
   438  					return
   439  				}
   440  				return
   441  			}
   442  
   443  			if !assert.NoError(t, jwt.Validate(t1, jwt.WithValidator(v))) {
   444  				return
   445  			}
   446  		})
   447  	}
   448  }
   449  

View as plain text