Skip to content

Commit

Permalink
Cache well-known responses to avoid making too much calls to the IdP (#…
Browse files Browse the repository at this point in the history
…251)

Signed-off-by: Ignasi Barrera <[email protected]>
  • Loading branch information
nacx authored Apr 17, 2024
1 parent 706f4e7 commit 1df3e7e
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 2 deletions.
2 changes: 2 additions & 0 deletions internal/authz/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1396,13 +1396,15 @@ func TestLoadWellKnownConfigMissingLogoutRedirectURI(t *testing.T) {
t.Cleanup(idpServer.Stop)

cfg := proto.Clone(dynamicOIDCConfig).(*oidcv1.OIDCConfig)
cfg.ConfigurationUri = "http://missing-logout/.well-known/openid-configuration"
require.ErrorIs(t, loadWellKnownConfig(idpServer.newHTTPClient(), cfg), ErrMissingLogoutRedirectURI)
}

func TestLoadWellKnownConfigError(t *testing.T) {
clock := oidc.Clock{}
tlsPool := internal.NewTLSConfigPool(context.Background())
cfg := proto.Clone(dynamicOIDCConfig).(*oidcv1.OIDCConfig)
cfg.ConfigurationUri = "http://stopped-server/.well-known/openid-configuration"
sessions := &mockSessionStoreFactory{store: oidc.NewMemoryStore(&clock, time.Hour, time.Hour)}
_, err := NewOIDCHandler(cfg, tlsPool, oidc.NewJWKSProvider(newConfigFor(basicOIDCConfig), tlsPool),
sessions, clock, oidc.NewStaticGenerator(newSessionID, newNonce, newState))
Expand Down
14 changes: 13 additions & 1 deletion internal/oidc/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,20 @@ type WellKnownConfig struct {
TokenRevocationEndpoint string `json:"token_revocation_endpoint"`
}

var (
// wellKnownConfigs is a map of issuer URL to the OIDC well-known configuration
// It is used to cache well-known configurations as they usually don't change. URLs are usually stable, and the only
// things that are subject to change are the signing keys, but those are already watched periodically by the JWKS fetcher.
wellKnownConfigs = make(map[string]WellKnownConfig)
)

// GetWellKnownConfig retrieves the OIDC well-known configuration from the given issuer URL.
func GetWellKnownConfig(client *http.Client, url string) (WellKnownConfig, error) {
cfg, ok := wellKnownConfigs[url]
if ok {
return cfg, nil
}

// Make a GET request to the well-known configuration endpoint
response, err := client.Get(url)
if err != nil {
Expand All @@ -55,10 +67,10 @@ func GetWellKnownConfig(client *http.Client, url string) (WellKnownConfig, error
}

// Decode the JSON response into the OIDCConfig struct
var cfg WellKnownConfig
if err = json.NewDecoder(response.Body).Decode(&cfg); err != nil {
return WellKnownConfig{}, err
}

wellKnownConfigs[url] = cfg
return cfg, nil
}
21 changes: 20 additions & 1 deletion internal/oidc/discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestWellKnownConfig(t *testing.T) {
{"ok", "http://example.com/.well-known/openid-configuration", validWellKnownJSON, false, validWellKnown},
{"not-found", "http://example.com/not-found", validWellKnownJSON, true, WellKnownConfig{}},
{"invalid-url", "invalid", validWellKnownJSON, true, WellKnownConfig{}},
{"invalid-json", "http://example.com/.well-known/openid-configuration", invalidWellKnownJSON, true, WellKnownConfig{}},
{"invalid-json", "http://example2.com/.well-known/openid-configuration", invalidWellKnownJSON, true, WellKnownConfig{}},
}

for _, tt := range tests {
Expand All @@ -74,6 +74,25 @@ func TestWellKnownConfig(t *testing.T) {
}
}

func TestWellKnownConfigCache(t *testing.T) {
s := newServer()
s.wellKnownConfig = validWellKnownJSON
s.Start()
c := s.newHTTPClient()

got, err := GetWellKnownConfig(c, "http://example.com/.well-known/openid-configuration")
require.NoError(t, err)
require.Equal(t, validWellKnown, got)

// Stop the server and run the well-known request again.
// It should succeed and return the cached value.
s.Stop()

got, err = GetWellKnownConfig(c, "http://example.com/.well-known/openid-configuration")
require.NoError(t, err)
require.Equal(t, validWellKnown, got)
}

type idpServer struct {
server *http.Server
listener *bufconn.Listener
Expand Down

0 comments on commit 1df3e7e

Please sign in to comment.