Skip to content

Commit 0fe9887

Browse files
committed
oidc: ignore cancellation of remote key set context
This has largely caused confusion. Detach the context from its parent and just use as a bag-of-values for configuration.
1 parent 308e778 commit 0fe9887

File tree

3 files changed

+97
-4
lines changed

3 files changed

+97
-4
lines changed

oidc/jwks.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,28 @@ func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time)
6464
if now == nil {
6565
now = time.Now
6666
}
67-
return &RemoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
67+
return &RemoteKeySet{
68+
jwksURL: jwksURL,
69+
now: now,
70+
// For historical reasons, this package uses contexts for configuration, not just
71+
// cancellation. In hindsight, this was a bad idea.
72+
//
73+
// Attemps to reason about how cancels should work with background requests have
74+
// largely lead to confusion. Use the context here as a config bag-of-values and
75+
// ignore the cancel function.
76+
ctx: context.WithoutCancel(ctx),
77+
}
6878
}
6979

7080
// RemoteKeySet is a KeySet implementation that validates JSON web tokens against
7181
// a jwks_uri endpoint.
7282
type RemoteKeySet struct {
7383
jwksURL string
74-
ctx context.Context
7584
now func() time.Time
7685

86+
// Used for configuration. Cancelation is ignored.
87+
ctx context.Context
88+
7789
// guard all other fields
7890
mu sync.RWMutex
7991

oidc/oidc_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,3 +675,84 @@ func TestVerifierAlg(t *testing.T) {
675675
}
676676

677677
}
678+
679+
func TestCanceledContext(t *testing.T) {
680+
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
681+
if err != nil {
682+
t.Fatalf("generating random key: %v", err)
683+
}
684+
pub := priv.Public()
685+
pubKey := jose.JSONWebKey{
686+
Algorithm: "ES256",
687+
Key: pub,
688+
Use: "sign",
689+
}
690+
691+
signingKey := jose.SigningKey{
692+
Algorithm: jose.ES256,
693+
Key: priv,
694+
}
695+
696+
ts := &testIssuer{
697+
algs: []string{"ES256"},
698+
jwks: &jose.JSONWebKeySet{
699+
Keys: []jose.JSONWebKey{
700+
pubKey,
701+
},
702+
},
703+
}
704+
srv := httptest.NewServer(ts)
705+
ts.baseURL = srv.URL
706+
707+
ctx, cancel := context.WithCancel(context.Background())
708+
709+
provider, err := NewProvider(ctx, srv.URL)
710+
if err != nil {
711+
t.Fatalf("creating provider: %v", err)
712+
}
713+
714+
// Explicitly cancel the context.
715+
cancel()
716+
717+
now := time.Now()
718+
719+
claims := struct {
720+
Iss string `json:"iss"`
721+
Sub string `json:"sub"`
722+
Aud string `json:"aud"`
723+
Exp int64 `json:"exp"`
724+
Iat int64 `json:"iat"`
725+
}{
726+
Iss: srv.URL,
727+
Sub: "test-user",
728+
Aud: "test-client",
729+
Exp: now.Add(time.Hour).Unix(),
730+
Iat: now.Add(-time.Hour).Unix(),
731+
}
732+
payload, err := json.Marshal(claims)
733+
if err != nil {
734+
t.Fatalf("marshaling claims: %v", err)
735+
}
736+
signer, err := jose.NewSigner(signingKey, nil)
737+
if err != nil {
738+
t.Fatalf("creating signing key: %v", err)
739+
}
740+
jws, err := signer.Sign(payload)
741+
if err != nil {
742+
t.Fatalf("signing token: %v", err)
743+
}
744+
rawIDToken, err := jws.CompactSerialize()
745+
if err != nil {
746+
t.Fatalf("serializing token: %v", err)
747+
}
748+
749+
config := &Config{
750+
ClientID: "test-client",
751+
}
752+
verifier := provider.Verifier(config)
753+
754+
ctx = context.Background()
755+
if _, err := verifier.Verify(ctx, rawIDToken); err != nil {
756+
t.Fatalf("verifying token: %v", err)
757+
}
758+
}

oidc/verify.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ type Config struct {
120120
}
121121

122122
// VerifierContext returns an IDTokenVerifier that uses the provider's key set to
123-
// verify JWTs. As opposed to Verifier, the context is used for all requests to
124-
// the upstream JWKs endpoint.
123+
// verify JWTs. As opposed to Verifier, the context is used to configure requests
124+
// to the upstream JWKs endpoint. The provided context's cancellation is ignored.
125125
func (p *Provider) VerifierContext(ctx context.Context, config *Config) *IDTokenVerifier {
126126
return p.newVerifier(NewRemoteKeySet(ctx, p.jwksURL), config)
127127
}

0 commit comments

Comments
 (0)