Repository: envoyproxy/ai-gateway
Pull request #415 (2025-02-24 21:41:33)

feat: store oidc as pointer for token providers
**Commit Message**

Set OIDC as a pointer in both OIDC Provider and ClientCredentialsTokenProvider's field. Updates occurring to OIDCProvider's oidcConfig will be "shared" with ClientCredentialsTokenProvider (cctProvider).

**Issue**

oidcProvider and cctProvider share the same oidc values on init (oidcProvider creates cctProvider and passes as param the same oidc value). The issue occurs in `FetchToken` as `token_endpoint` of oidc gets updated in oidcProvider and the subsequent call to cctProvider's `FetchToken` expects `token_endpoint` to be propagated. The value of `token_endpoint` in cctProvider is nil as no value is set to it.

Without propagating the value over, the error that shows up is: `failed to get token: fail to get oauth2 token Post "token_endpoint": unsupported protocol scheme ""`. 

**Testing**
Updated the original FetchToken test to not set TokenEndpoint as part of the oidc value. Original code results in the same error, but new changes passed the test.


Patch:
diff --git a/internal/controller/oauth/client_credentials_token_provider.go b/internal/controller/oauth/client_credentials_token_provider.go
index 0043f28..ef3cd1b 100644
--- a/internal/controller/oauth/client_credentials_token_provider.go
+++ b/internal/controller/oauth/client_credentials_token_provider.go
@@ -23,12 +23,13 @@ const tokenTimeoutDuration = time.Minute
 
 // ClientCredentialsTokenProvider implements the standard OAuth2 client credentials flow.
 type ClientCredentialsTokenProvider struct {
-	client     client.Client
-	oidcConfig egv1a1.OIDC
+	client client.Client
+	// oidcConfig will be in sync with the caller of newClientCredentialsTokenProvider.
+	oidcConfig *egv1a1.OIDC
 }
 
 // newClientCredentialsProvider creates a new client credentials provider.
-func newClientCredentialsProvider(cl client.Client, oidcConfig egv1a1.OIDC) *ClientCredentialsTokenProvider {
+func newClientCredentialsProvider(cl client.Client, oidcConfig *egv1a1.OIDC) *ClientCredentialsTokenProvider {
 	return &ClientCredentialsTokenProvider{
 		client:     cl,
 		oidcConfig: oidcConfig,
diff --git a/internal/controller/oauth/oidc_provider.go b/internal/controller/oauth/oidc_provider.go
index c36ad94..9cdaccb 100644
--- a/internal/controller/oauth/oidc_provider.go
+++ b/internal/controller/oauth/oidc_provider.go
@@ -18,14 +18,15 @@ import (
 // OIDCProvider extends ClientCredentialsTokenProvider with OIDC support.
 type OIDCProvider struct {
 	tokenProvider TokenProvider
-	oidcConfig    egv1a1.OIDC
+	// oidcConfig will be in sync with clientCredentialsProvider OIDC.
+	oidcConfig *egv1a1.OIDC
 }
 
 // NewOIDCProvider creates a new OIDC-aware provider.
 func NewOIDCProvider(client client.Client, oidcConfig egv1a1.OIDC) *OIDCProvider {
 	return &OIDCProvider{
-		tokenProvider: newClientCredentialsProvider(client, oidcConfig),
-		oidcConfig:    oidcConfig,
+		tokenProvider: newClientCredentialsProvider(client, &oidcConfig),
+		oidcConfig:    &oidcConfig,
 	}
 }
 


Test patch:
diff --git a/internal/controller/oauth/client_credentials_token_provider_test.go b/internal/controller/oauth/client_credentials_token_provider_test.go
index 35a8eb9..f270677 100644
--- a/internal/controller/oauth/client_credentials_token_provider_test.go
+++ b/internal/controller/oauth/client_credentials_token_provider_test.go
@@ -54,14 +54,14 @@ func TestClientCredentialsProvider_FetchToken(t *testing.T) {
 	require.NoError(t, err)
 
 	namespaceRef := gwapiv1.Namespace(secretNamespace)
-	clientCredentialProvider := newClientCredentialsProvider(cl, egv1a1.OIDC{})
+	clientCredentialProvider := newClientCredentialsProvider(cl, &egv1a1.OIDC{})
 	require.NotNil(t, clientCredentialProvider)
 
 	_, err = clientCredentialProvider.FetchToken(t.Context())
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "oidc-client-secret namespace is nil")
 
-	clientCredentialProvider = newClientCredentialsProvider(cl, egv1a1.OIDC{
+	clientCredentialProvider = newClientCredentialsProvider(cl, &egv1a1.OIDC{
 		Provider: egv1a1.OIDCProvider{
 			Issuer:        tokenServer.URL,
 			TokenEndpoint: &tokenServer.URL,
diff --git a/internal/controller/oauth/oidc_provider_test.go b/internal/controller/oauth/oidc_provider_test.go
index 0367e8b..4ecdecb 100644
--- a/internal/controller/oauth/oidc_provider_test.go
+++ b/internal/controller/oauth/oidc_provider_test.go
@@ -8,6 +8,7 @@ package oauth
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -123,11 +124,6 @@ func TestOIDCProvider_GetOIDCProviderConfig(t *testing.T) {
 }
 
 func TestOIDCProvider_FetchToken(t *testing.T) {
-	oidcServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
-		_, err := w.Write([]byte(`{"issuer": "issuer", "token_endpoint": "token_endpoint", "authorization_endpoint": "authorization_endpoint", "jwks_uri": "jwks_uri", "scopes_supported": ["one", "openid"]}`))
-		require.NoError(t, err)
-	}))
-	defer oidcServer.Close()
 	tokenServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
 		w.Header().Add("Content-Type", "application/json")
 		b, err := json.Marshal(oauth2.Token{AccessToken: "token", TokenType: "Bearer", ExpiresIn: int64(3600)})
@@ -137,6 +133,13 @@ func TestOIDCProvider_FetchToken(t *testing.T) {
 	}))
 	defer tokenServer.Close()
 
+	oidcServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+		res := fmt.Sprintf(`{"issuer": "issuer", "token_endpoint": "%s", "authorization_endpoint": "authorization_endpoint", "jwks_uri": "jwks_uri", "scopes_supported": ["one", "openid"]}`, tokenServer.URL)
+		_, err := w.Write([]byte(res))
+		require.NoError(t, err)
+	}))
+	defer oidcServer.Close()
+
 	scheme := runtime.NewScheme()
 	scheme.AddKnownTypes(corev1.SchemeGroupVersion,
 		&corev1.Secret{},
@@ -160,8 +163,7 @@ func TestOIDCProvider_FetchToken(t *testing.T) {
 	namespaceRef := gwapiv1.Namespace(secretNamespace)
 	oidc := egv1a1.OIDC{
 		Provider: egv1a1.OIDCProvider{
-			Issuer:        oidcServer.URL,
-			TokenEndpoint: &tokenServer.URL,
+			Issuer: oidcServer.URL,
 		},
 		ClientID: "some-client-id",
 		ClientSecret: gwapiv1.SecretObjectReference{