// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package jwt import ( "fmt" "time" ) const ( jwtMaxClockSkewMinutes = 10 ) // ValidatorOpts define validation options for JWT validators. type ValidatorOpts struct { ExpectedTypeHeader *string ExpectedIssuer *string ExpectedAudience *string IgnoreTypeHeader bool IgnoreAudiences bool IgnoreIssuer bool AllowMissingExpiration bool ExpectIssuedInThePast bool ClockSkew time.Duration FixedNow time.Time // Deprecated: Use ExpectedAudience instead. ExpectedAudiences *string } // Validator defines how JSON Web Tokens (JWT) should be validated. type Validator struct { opts ValidatorOpts } // NewValidator creates a new Validator. func NewValidator(opts *ValidatorOpts) (*Validator, error) { if opts == nil { return nil, fmt.Errorf("ValidatorOpts can't be nil") } if opts.ExpectedAudiences != nil { if opts.ExpectedAudience != nil { return nil, fmt.Errorf("ExpectedAudiences and ExpectedAudience can't be set at the same time") } opts.ExpectedAudience = opts.ExpectedAudiences opts.ExpectedAudiences = nil } if opts.ExpectedTypeHeader != nil && opts.IgnoreTypeHeader { return nil, fmt.Errorf("ExpectedTypeHeader and IgnoreTypeHeader cannot be used together") } if opts.ExpectedIssuer != nil && opts.IgnoreIssuer { return nil, fmt.Errorf("ExpectedIssuer and IgnoreIssuer cannot be used together") } if opts.ExpectedAudience != nil && opts.IgnoreAudiences { return nil, fmt.Errorf("ExpectedAudience and IgnoreAudience cannot be used together") } if opts.ClockSkew.Minutes() > jwtMaxClockSkewMinutes { return nil, fmt.Errorf("clock skew too large, max is %d minutes", jwtMaxClockSkewMinutes) } return &Validator{ opts: *opts, }, nil } // Validate validates a rawJWT according to the options provided. func (v *Validator) Validate(rawJWT *RawJWT) error { if rawJWT == nil { return fmt.Errorf("rawJWT can't be nil") } if err := v.validateTimestamps(rawJWT); err != nil { return err } if err := v.validateTypeHeader(rawJWT); err != nil { return fmt.Errorf("validating type header: %v", err) } if err := v.validateAudiences(rawJWT); err != nil { return fmt.Errorf("validating audience claim: %v", err) } if err := v.validateIssuer(rawJWT); err != nil { return fmt.Errorf("validating issuer claim: %v", err) } return nil } func (v *Validator) validateTimestamps(rawJWT *RawJWT) error { now := time.Now() if !v.opts.FixedNow.IsZero() { now = v.opts.FixedNow } if !rawJWT.HasExpiration() && !v.opts.AllowMissingExpiration { return fmt.Errorf("token doesn't have an expiration set") } if rawJWT.HasExpiration() { exp, err := rawJWT.ExpiresAt() if err != nil { return err } if !exp.After(now.Add(-v.opts.ClockSkew)) { return errJwtExpired } } if rawJWT.HasNotBefore() { nbf, err := rawJWT.NotBefore() if err != nil { return err } if nbf.After(now.Add(v.opts.ClockSkew)) { return fmt.Errorf("token cannot be used yet") } } if v.opts.ExpectIssuedInThePast { iat, err := rawJWT.IssuedAt() if err != nil { return err } if iat.After(now.Add(v.opts.ClockSkew)) { return fmt.Errorf("token has an invalid iat claim in the future") } } return nil } func (v *Validator) validateTypeHeader(rawJWT *RawJWT) error { skip, err := validateFieldPresence(v.opts.IgnoreTypeHeader, rawJWT.HasTypeHeader(), v.opts.ExpectedTypeHeader != nil) if err != nil { return err } if skip { return nil } typeHeader, err := rawJWT.TypeHeader() if err != nil { return err } if typeHeader != *v.opts.ExpectedTypeHeader { return fmt.Errorf("wrong 'type header' type") } return nil } func (v *Validator) validateIssuer(rawJWT *RawJWT) error { skip, err := validateFieldPresence(v.opts.IgnoreIssuer, rawJWT.HasIssuer(), v.opts.ExpectedIssuer != nil) if err != nil { return err } if skip { return nil } issuer, err := rawJWT.Issuer() if err != nil { return err } if issuer != *v.opts.ExpectedIssuer { return fmt.Errorf("got %s, want %s", issuer, *v.opts.ExpectedIssuer) } return nil } func (v *Validator) validateAudiences(rawJWT *RawJWT) error { skip, err := validateFieldPresence(v.opts.IgnoreAudiences, rawJWT.HasAudiences(), v.opts.ExpectedAudience != nil) if err != nil { return err } if skip { return nil } audiences, err := rawJWT.Audiences() if err != nil { return err } for i, aud := range audiences { if aud == *v.opts.ExpectedAudience { break } if i == len(audiences)-1 { return fmt.Errorf("%s not found", *v.opts.ExpectedAudience) } } return nil } func validateFieldPresence(ignore bool, isPresent bool, isExpected bool) (bool, error) { if ignore { return true, nil } if !isExpected && !isPresent { return true, nil } if !isExpected && isPresent { return false, fmt.Errorf("token has claim but validator doesn't expect it") } if isExpected && !isPresent { return false, fmt.Errorf("claim was expected but isn't present") } return false, nil }