go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/openid/id_token.go (about) 1 // Copyright 2017 The LUCI Authors. 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 package openid 16 17 import ( 18 "context" 19 "time" 20 21 "go.chromium.org/luci/auth/jwt" 22 "go.chromium.org/luci/common/clock" 23 "go.chromium.org/luci/common/errors" 24 ) 25 26 // IDToken is a verified deserialized ID token. 27 // 28 // See https://developers.google.com/identity/protocols/OpenIDConnect. 29 type IDToken struct { 30 Iss string `json:"iss"` 31 AtHash string `json:"at_hash"` 32 EmailVerified bool `json:"email_verified"` 33 Sub string `json:"sub"` 34 Azp string `json:"azp"` 35 Email string `json:"email"` 36 Profile string `json:"profile"` 37 Picture string `json:"picture"` 38 Name string `json:"name"` 39 Aud string `json:"aud"` 40 Iat int64 `json:"iat"` 41 Exp int64 `json:"exp"` 42 Nonce string `json:"nonce"` 43 Hd string `json:"hd"` 44 45 // This section is present only for tokens generated via GCE Metadata server 46 // in "full" format. 47 Google struct { 48 ComputeEngine struct { 49 InstanceCreationTimestamp int64 `json:"instance_creation_timestamp"` 50 InstanceID string `json:"instance_id"` 51 InstanceName string `json:"instance_name"` 52 ProjectID string `json:"project_id"` 53 ProjectNumber int64 `json:"project_number"` 54 Zone string `json:"zone"` 55 } `json:"compute_engine"` 56 } `json:"google"` 57 } 58 59 const allowedClockSkew = 30 * time.Second 60 61 // VerifyIDToken deserializes and verifies the ID token. 62 // 63 // It checks the signature, expiration time and verifies fields `iss` and 64 // `email_verified`. 65 // 66 // It checks `aud` and `sub` are present, but does NOT verify them any further. 67 // It is the caller's responsibility to do so. 68 // 69 // This is a fast local operation. 70 func VerifyIDToken(ctx context.Context, token string, keys jwt.SignatureVerifier, issuer string) (*IDToken, error) { 71 // See https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken 72 73 tok := &IDToken{} 74 if err := jwt.VerifyAndDecode(token, tok, keys); err != nil { 75 return nil, errors.Annotate(err, "bad ID token").Err() 76 } 77 78 exp := time.Unix(tok.Exp, 0) 79 now := clock.Now(ctx) 80 81 switch { 82 case tok.Iss != issuer && "https://"+tok.Iss != issuer: 83 return nil, errors.Reason("bad ID token: expecting issuer %q, got %q", issuer, tok.Iss).Err() 84 case exp.Add(allowedClockSkew).Before(now): 85 return nil, errors.Reason("bad ID token: expired %s ago", now.Sub(exp)).Err() 86 case !tok.EmailVerified: 87 return nil, errors.Reason("bad ID token: the email %q is not verified", tok.Email).Err() 88 case tok.Aud == "": 89 return nil, errors.Reason("bad ID token: the audience is missing").Err() 90 case tok.Sub == "": 91 return nil, errors.Reason("bad ID token: the subject is missing").Err() 92 } 93 94 return tok, nil 95 }