go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/openid/id_token_test.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 "strings" 20 "testing" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 "go.chromium.org/luci/common/clock/testclock" 25 26 "go.chromium.org/luci/server/auth/signing/signingtest" 27 28 . "github.com/smartystreets/goconvey/convey" 29 . "go.chromium.org/luci/common/testing/assertions" 30 ) 31 32 func TestVerifyIDToken(t *testing.T) { 33 t.Parallel() 34 35 ctx := context.Background() 36 ctx, tc := testclock.UseTime(ctx, time.Unix(1442540000, 0)) 37 38 // Prepare the signing keys. 39 const issuer = "https://issuer.example.com" 40 const signingKeyID = "signing-key" 41 42 signer := signingtest.NewSigner(nil) 43 jwks, _ := NewJSONWebKeySet(jwksForTest(signingKeyID, &signer.KeyForTest().PublicKey)) 44 45 newToken := func() *IDToken { 46 return &IDToken{ 47 Iss: issuer, 48 EmailVerified: true, 49 Sub: "user_id_sub", 50 Email: "user@example.com", 51 Name: "Some Dude", 52 Picture: "https://picture/url/s64/photo.jpg", 53 Aud: "client_id", 54 Iat: clock.Now(ctx).Unix(), 55 Exp: clock.Now(ctx).Add(time.Hour).Unix(), 56 } 57 } 58 signToken := func(t *IDToken) string { 59 return idTokenForTest(ctx, t, signingKeyID, signer) 60 } 61 verifyToken := func(token string) (*IDToken, error) { 62 return VerifyIDToken(ctx, token, jwks, issuer) 63 } 64 65 Convey("Happy path", t, func() { 66 original := newToken() 67 parsed, err := verifyToken(signToken(original)) 68 So(err, ShouldBeNil) 69 So(parsed, ShouldResemble, original) 70 71 // Alternative issuer form. 72 original.Iss = strings.TrimPrefix(issuer, "https://") 73 parsed, err = verifyToken(signToken(original)) 74 So(err, ShouldBeNil) 75 So(parsed, ShouldResemble, original) 76 }) 77 78 Convey("Bad JWT", t, func() { 79 _, err := verifyToken("IMANOTAJWT") 80 So(err, ShouldErrLike, "bad JWT") 81 }) 82 83 Convey("Bad body (not JSON)", t, func() { 84 _, err := verifyToken(jwtForTest(ctx, []byte("IAMNOTJSON"), signingKeyID, signer)) 85 So(err, ShouldErrLike, "can't deserialize JSON") 86 }) 87 88 Convey("Bad issuer", t, func() { 89 tok := newToken() 90 tok.Iss = "something else" 91 _, err := verifyToken(signToken(tok)) 92 So(err, ShouldErrLike, "expecting issuer") 93 }) 94 95 Convey("Unverified email", t, func() { 96 tok := newToken() 97 tok.EmailVerified = false 98 _, err := verifyToken(signToken(tok)) 99 So(err, ShouldErrLike, "is not verified") 100 }) 101 102 Convey("No audience", t, func() { 103 tok := newToken() 104 tok.Aud = "" 105 _, err := verifyToken(signToken(tok)) 106 So(err, ShouldErrLike, "the audience is missing") 107 }) 108 109 Convey("No subject", t, func() { 110 tok := newToken() 111 tok.Sub = "" 112 _, err := verifyToken(signToken(tok)) 113 So(err, ShouldErrLike, "the subject is missing") 114 }) 115 116 Convey("Expired", t, func() { 117 tok := signToken(newToken()) 118 119 // Still good enough after 1h due to clock skew protection. 120 tc.Add(time.Hour) 121 _, err := verifyToken(tok) 122 So(err, ShouldBeNil) 123 124 // Some moments later is considered expired for real. 125 tc.Add(allowedClockSkew + time.Second) 126 _, err = verifyToken(tok) 127 So(err, ShouldErrLike, "expired") 128 }) 129 }