@@ -28,6 +28,7 @@ import (
2828 "github.com/MicahParks/keyfunc/v3"
2929 "github.com/golang-jwt/jwt/v5"
3030 "github.com/googleapis/genai-toolbox/internal/auth"
31+ "github.com/googleapis/genai-toolbox/internal/util"
3132)
3233
3334const AuthServiceType string = "generic"
@@ -71,6 +72,22 @@ func (cfg Config) Initialize() (auth.AuthService, error) {
7172 return a , nil
7273}
7374
75+ func newSecureHTTPClient () * http.Client {
76+ return & http.Client {
77+ Timeout : 10 * time .Second ,
78+ Transport : & http.Transport {
79+ ForceAttemptHTTP2 : true ,
80+ MaxIdleConns : 10 ,
81+ IdleConnTimeout : 90 * time .Second ,
82+ TLSHandshakeTimeout : 5 * time .Second ,
83+ ExpectContinueTimeout : 1 * time .Second ,
84+ },
85+ CheckRedirect : func (req * http.Request , via []* http.Request ) error {
86+ return http .ErrUseLastResponse
87+ },
88+ }
89+ }
90+
7491func discoverJWKSURL (AuthorizationServer string ) (string , error ) {
7592 u , err := url .Parse (AuthorizationServer )
7693 if err != nil {
@@ -86,20 +103,7 @@ func discoverJWKSURL(AuthorizationServer string) (string, error) {
86103 }
87104
88105 // HTTP Client
89- client := & http.Client {
90- Timeout : 10 * time .Second ,
91- Transport : & http.Transport {
92- ForceAttemptHTTP2 : true ,
93- MaxIdleConns : 10 ,
94- IdleConnTimeout : 90 * time .Second ,
95- TLSHandshakeTimeout : 5 * time .Second ,
96- ExpectContinueTimeout : 1 * time .Second ,
97- },
98- // Prevent redirect loops or redirects to internal sites
99- CheckRedirect : func (req * http.Request , via []* http.Request ) error {
100- return http .ErrUseLastResponse
101- },
102- }
106+ client := newSecureHTTPClient ()
103107
104108 resp , err := client .Get (oidcConfigURL )
105109 if err != nil {
@@ -295,7 +299,15 @@ func (a AuthService) validateJwtToken(ctx context.Context, tokenStr string) erro
295299}
296300
297301func (a AuthService ) validateOpaqueToken (ctx context.Context , tokenStr string ) error {
298- introspectionURL := a .AuthorizationServer + "/introspect"
302+ logger , err := util .LoggerFromContext (ctx )
303+ if err != nil {
304+ return fmt .Errorf ("failed to get logger from context: %w" , err )
305+ }
306+
307+ introspectionURL , err := url .JoinPath (a .AuthorizationServer , "introspect" )
308+ if err != nil {
309+ return fmt .Errorf ("failed to construct introspection URL: %w" , err )
310+ }
299311
300312 data := url.Values {}
301313 data .Set ("token" , tokenStr )
@@ -308,17 +320,17 @@ func (a AuthService) validateOpaqueToken(ctx context.Context, tokenStr string) e
308320 req .Header .Set ("Accept" , "application/json" )
309321
310322 // Send request to auth server's introspection endpoint
311- client := & http.Client {
312- Timeout : 10 * time .Second ,
313- }
323+ client := newSecureHTTPClient ()
314324
315325 resp , err := client .Do (req )
316326 if err != nil {
327+ logger .ErrorContext (ctx , "failed to call introspection endpoint: %v" , err )
317328 return & MCPAuthError {Code : http .StatusInternalServerError , Message : fmt .Sprintf ("failed to call introspection endpoint: %v" , err ), ScopesRequired : a .ScopesRequired }
318329 }
319330 defer resp .Body .Close ()
320331
321332 if resp .StatusCode != http .StatusOK {
333+ logger .WarnContext (ctx , "introspection failed with status: %d" , resp .StatusCode )
322334 return & MCPAuthError {Code : http .StatusUnauthorized , Message : fmt .Sprintf ("introspection failed with status: %d" , resp .StatusCode ), ScopesRequired : a .ScopesRequired }
323335 }
324336
@@ -339,16 +351,20 @@ func (a AuthService) validateOpaqueToken(ctx context.Context, tokenStr string) e
339351 }
340352
341353 if ! introspectResp .Active {
354+ logger .InfoContext (ctx , "token is not active" )
342355 return & MCPAuthError {Code : http .StatusUnauthorized , Message : "token is not active" , ScopesRequired : a .ScopesRequired }
343356 }
344357
345358 // Verify audience (client_id)
346359 if a .Audience != "" && introspectResp .ClientId != a .Audience {
360+ logger .WarnContext (ctx , "audience validation failed: expected %s, got %s" , a .Audience , introspectResp .ClientId )
347361 return & MCPAuthError {Code : http .StatusUnauthorized , Message : "audience validation failed" , ScopesRequired : a .ScopesRequired }
348362 }
349363
350- // Verify expiration
351- if introspectResp .Exp > 0 && time .Now ().Unix () > introspectResp .Exp {
364+ // Verify expiration (with 1 minute leeway) to account for potential time difference between Toolbox and the auth server
365+ const leeway = 60
366+ if introspectResp .Exp > 0 && time .Now ().Unix () > (introspectResp .Exp + leeway ) {
367+ logger .WarnContext (ctx , "token has expired: exp=%d, now=%d" , introspectResp .Exp , time .Now ().Unix ())
352368 return & MCPAuthError {Code : http .StatusUnauthorized , Message : "token has expired" , ScopesRequired : a .ScopesRequired }
353369 }
354370
0 commit comments