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