1// Copyright 2022 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14// 15//////////////////////////////////////////////////////////////////////////////// 16 17package jwt 18 19import ( 20 "fmt" 21 "time" 22) 23 24const ( 25 jwtMaxClockSkewMinutes = 10 26) 27 28// ValidatorOpts define validation options for JWT validators. 29type ValidatorOpts struct { 30 ExpectedTypeHeader *string 31 ExpectedIssuer *string 32 ExpectedAudience *string 33 34 IgnoreTypeHeader bool 35 IgnoreAudiences bool 36 IgnoreIssuer bool 37 38 AllowMissingExpiration bool 39 ExpectIssuedInThePast bool 40 41 ClockSkew time.Duration 42 FixedNow time.Time 43 44 // Deprecated: Use ExpectedAudience instead. 45 ExpectedAudiences *string 46} 47 48// Validator defines how JSON Web Tokens (JWT) should be validated. 49type Validator struct { 50 opts ValidatorOpts 51} 52 53// NewValidator creates a new Validator. 54func NewValidator(opts *ValidatorOpts) (*Validator, error) { 55 if opts == nil { 56 return nil, fmt.Errorf("ValidatorOpts can't be nil") 57 } 58 if opts.ExpectedAudiences != nil { 59 if opts.ExpectedAudience != nil { 60 return nil, fmt.Errorf("ExpectedAudiences and ExpectedAudience can't be set at the same time") 61 } 62 opts.ExpectedAudience = opts.ExpectedAudiences 63 opts.ExpectedAudiences = nil 64 } 65 if opts.ExpectedTypeHeader != nil && opts.IgnoreTypeHeader { 66 return nil, fmt.Errorf("ExpectedTypeHeader and IgnoreTypeHeader cannot be used together") 67 } 68 if opts.ExpectedIssuer != nil && opts.IgnoreIssuer { 69 return nil, fmt.Errorf("ExpectedIssuer and IgnoreIssuer cannot be used together") 70 } 71 if opts.ExpectedAudience != nil && opts.IgnoreAudiences { 72 return nil, fmt.Errorf("ExpectedAudience and IgnoreAudience cannot be used together") 73 } 74 if opts.ClockSkew.Minutes() > jwtMaxClockSkewMinutes { 75 return nil, fmt.Errorf("clock skew too large, max is %d minutes", jwtMaxClockSkewMinutes) 76 } 77 return &Validator{ 78 opts: *opts, 79 }, nil 80} 81 82// Validate validates a rawJWT according to the options provided. 83func (v *Validator) Validate(rawJWT *RawJWT) error { 84 if rawJWT == nil { 85 return fmt.Errorf("rawJWT can't be nil") 86 } 87 if err := v.validateTimestamps(rawJWT); err != nil { 88 return err 89 } 90 if err := v.validateTypeHeader(rawJWT); err != nil { 91 return fmt.Errorf("validating type header: %v", err) 92 } 93 if err := v.validateAudiences(rawJWT); err != nil { 94 return fmt.Errorf("validating audience claim: %v", err) 95 } 96 if err := v.validateIssuer(rawJWT); err != nil { 97 return fmt.Errorf("validating issuer claim: %v", err) 98 } 99 return nil 100} 101 102func (v *Validator) validateTimestamps(rawJWT *RawJWT) error { 103 now := time.Now() 104 if !v.opts.FixedNow.IsZero() { 105 now = v.opts.FixedNow 106 } 107 108 if !rawJWT.HasExpiration() && !v.opts.AllowMissingExpiration { 109 return fmt.Errorf("token doesn't have an expiration set") 110 } 111 if rawJWT.HasExpiration() { 112 exp, err := rawJWT.ExpiresAt() 113 if err != nil { 114 return err 115 } 116 if !exp.After(now.Add(-v.opts.ClockSkew)) { 117 return errJwtExpired 118 } 119 } 120 if rawJWT.HasNotBefore() { 121 nbf, err := rawJWT.NotBefore() 122 if err != nil { 123 return err 124 } 125 if nbf.After(now.Add(v.opts.ClockSkew)) { 126 return fmt.Errorf("token cannot be used yet") 127 } 128 } 129 if v.opts.ExpectIssuedInThePast { 130 iat, err := rawJWT.IssuedAt() 131 if err != nil { 132 return err 133 } 134 if iat.After(now.Add(v.opts.ClockSkew)) { 135 return fmt.Errorf("token has an invalid iat claim in the future") 136 } 137 } 138 return nil 139} 140 141func (v *Validator) validateTypeHeader(rawJWT *RawJWT) error { 142 skip, err := validateFieldPresence(v.opts.IgnoreTypeHeader, rawJWT.HasTypeHeader(), v.opts.ExpectedTypeHeader != nil) 143 if err != nil { 144 return err 145 } 146 if skip { 147 return nil 148 } 149 typeHeader, err := rawJWT.TypeHeader() 150 if err != nil { 151 return err 152 } 153 if typeHeader != *v.opts.ExpectedTypeHeader { 154 return fmt.Errorf("wrong 'type header' type") 155 } 156 return nil 157} 158 159func (v *Validator) validateIssuer(rawJWT *RawJWT) error { 160 skip, err := validateFieldPresence(v.opts.IgnoreIssuer, rawJWT.HasIssuer(), v.opts.ExpectedIssuer != nil) 161 if err != nil { 162 return err 163 } 164 if skip { 165 return nil 166 } 167 issuer, err := rawJWT.Issuer() 168 if err != nil { 169 return err 170 } 171 if issuer != *v.opts.ExpectedIssuer { 172 return fmt.Errorf("got %s, want %s", issuer, *v.opts.ExpectedIssuer) 173 } 174 return nil 175} 176 177func (v *Validator) validateAudiences(rawJWT *RawJWT) error { 178 skip, err := validateFieldPresence(v.opts.IgnoreAudiences, rawJWT.HasAudiences(), v.opts.ExpectedAudience != nil) 179 if err != nil { 180 return err 181 } 182 if skip { 183 return nil 184 } 185 audiences, err := rawJWT.Audiences() 186 if err != nil { 187 return err 188 } 189 for i, aud := range audiences { 190 if aud == *v.opts.ExpectedAudience { 191 break 192 } 193 if i == len(audiences)-1 { 194 return fmt.Errorf("%s not found", *v.opts.ExpectedAudience) 195 } 196 } 197 return nil 198} 199 200func validateFieldPresence(ignore bool, isPresent bool, isExpected bool) (bool, error) { 201 if ignore { 202 return true, nil 203 } 204 if !isExpected && !isPresent { 205 return true, nil 206 } 207 if !isExpected && isPresent { 208 return false, fmt.Errorf("token has claim but validator doesn't expect it") 209 } 210 if isExpected && !isPresent { 211 return false, fmt.Errorf("claim was expected but isn't present") 212 } 213 return false, nil 214} 215