@@ -29,33 +29,48 @@ public partial class BasicOAuthAuthorizationProvider(
2929 Uri ? redirectUri = null ,
3030 IEnumerable < string > ? scopes = null ) : IMcpAuthorizationProvider
3131{
32+ // Cache for tokens, keyed by the canonical resource URI from resource metadata
3233 private readonly ConcurrentDictionary < string , TokenContainer > _tokenCache = new ( ) ;
34+
35+ // Cache for resource metadata, keyed by the request URI
36+ private readonly ConcurrentDictionary < string , ProtectedResourceMetadata > _resourceMetadataCache = new ( ) ;
37+
3338 private readonly Uri _serverUrl = serverUrl ?? throw new ArgumentNullException ( nameof ( serverUrl ) ) ;
3439 private readonly Uri _redirectUri = redirectUri ?? new Uri ( "http://localhost:8080/callback" ) ;
3540 private readonly IEnumerable < string > _scopes = scopes ?? Array . Empty < string > ( ) ;
3641
3742 /// <inheritdoc />
3843 public async Task < string ? > GetCredentialAsync ( Uri resourceUri , CancellationToken cancellationToken = default )
3944 {
40- Console . WriteLine ( $ "Getting authentication token for { resourceUri } ") ;
45+ Console . WriteLine ( $ "Getting credential for resource URI: { resourceUri } ") ;
46+
47+ // First, get the resource metadata to determine the canonical resource URI
48+ var resourceMetadata = await GetCachedResourceMetadataAsync ( resourceUri , cancellationToken ) ;
49+ if ( resourceMetadata == null )
50+ {
51+ Console . WriteLine ( "Failed to get resource metadata, cannot authenticate" ) ;
52+ return null ;
53+ }
54+
55+ // Use the canonical resource URI from the metadata as the cache key
56+ string resourceKey = resourceMetadata . Resource . ToString ( ) ;
57+ Console . WriteLine ( $ "Using canonical resource key: { resourceKey } ") ;
4158
4259 // Check if we have a valid cached token
43- string resourceKey = resourceUri . ToString ( ) ;
4460 if ( _tokenCache . TryGetValue ( resourceKey , out var tokenInfo ) )
4561 {
4662 // Check if the token is still valid or needs to be refreshed
4763 if ( tokenInfo . ExpiresAt > DateTimeOffset . UtcNow . AddMinutes ( 5 ) ) // 5-minute buffer
4864 {
4965 Console . WriteLine ( "Using cached token" ) ;
66+ // Return just the access token, not the token type + access token
5067 return tokenInfo . AccessToken ;
5168 }
5269 else if ( ! string . IsNullOrEmpty ( tokenInfo . RefreshToken ) )
5370 {
5471 Console . WriteLine ( "Token expired, attempting to refresh" ) ;
5572
56- // Get the authorization server metadata for the resource
57- var resourceMetadata = await GetResourceMetadataAsync ( resourceUri , cancellationToken ) ;
58- if ( resourceMetadata ? . AuthorizationServers ? . Count > 0 )
73+ if ( resourceMetadata . AuthorizationServers ? . Count > 0 )
5974 {
6075 var authServerUrl = resourceMetadata . AuthorizationServers [ 0 ] ;
6176 var authServerMetadata = await AuthorizationServerUtils . FetchAuthorizationServerMetadataAsync (
@@ -73,6 +88,7 @@ public partial class BasicOAuthAuthorizationProvider(
7388 {
7489 _tokenCache [ resourceKey ] = refreshedToken ;
7590 Console . WriteLine ( "Token refreshed successfully" ) ;
91+ // Return just the access token, not the token type + access token
7692 return refreshedToken . AccessToken ;
7793 }
7894 else
@@ -91,11 +107,47 @@ public partial class BasicOAuthAuthorizationProvider(
91107 _tokenCache . TryRemove ( resourceKey , out _ ) ;
92108 }
93109
94- // We don't have a valid token and need to get a new one
95- Console . WriteLine ( "No valid token available" ) ;
110+ // We don't have a valid token - let the 401 handler trigger the auth flow
111+ Console . WriteLine ( "No valid token available for: " + resourceKey ) ;
96112 return null ;
97113 }
98-
114+
115+ /// <summary>
116+ /// Gets resource metadata, using the cache if available.
117+ /// </summary>
118+ /// <param name="resourceUri">The URI of the protected resource.</param>
119+ /// <param name="cancellationToken">A token to cancel the operation.</param>
120+ /// <returns>The protected resource metadata.</returns>
121+ private async Task < ProtectedResourceMetadata ? > GetCachedResourceMetadataAsync ( Uri resourceUri , CancellationToken cancellationToken )
122+ {
123+ string requestUriKey = resourceUri . ToString ( ) ;
124+
125+ // Check if we already have cached metadata for this URI
126+ if ( _resourceMetadataCache . TryGetValue ( requestUriKey , out var cachedMetadata ) )
127+ {
128+ Console . WriteLine ( $ "Using cached resource metadata for: { requestUriKey } ") ;
129+ return cachedMetadata ;
130+ }
131+
132+ // If not in cache, fetch the metadata
133+ var metadata = await GetResourceMetadataAsync ( resourceUri , cancellationToken ) ;
134+ if ( metadata != null )
135+ {
136+ // Cache the metadata using the request URI as the key
137+ _resourceMetadataCache [ requestUriKey ] = metadata ;
138+
139+ // Also cache using any alternate forms of the URI that might be used
140+ if ( resourceUri . ToString ( ) != metadata . Resource . ToString ( ) )
141+ {
142+ _resourceMetadataCache [ metadata . Resource . ToString ( ) ] = metadata ;
143+ }
144+
145+ Console . WriteLine ( $ "Cached resource metadata for { requestUriKey } -> { metadata . Resource } ") ;
146+ }
147+
148+ return metadata ;
149+ }
150+
99151 /// <summary>
100152 /// Refreshes an OAuth token using the refresh token.
101153 /// </summary>
@@ -145,15 +197,9 @@ public partial class BasicOAuthAuthorizationProvider(
145197
146198 if ( tokenResponse != null )
147199 {
148- // Set the time when the token was obtained
200+ // Set the time when the token was obtained - ExpiresAt will be calculated automatically
149201 tokenResponse . ObtainedAt = DateTimeOffset . UtcNow ;
150202
151- // Calculate expiration time if not set
152- if ( tokenResponse . ExpiresIn > 0 && tokenResponse . ExpiresAt == default )
153- {
154- tokenResponse . ExpiresAt = tokenResponse . ObtainedAt . AddSeconds ( tokenResponse . ExpiresIn ) ;
155- }
156-
157203 // Preserve the refresh token if the response doesn't include a new one
158204 if ( string . IsNullOrEmpty ( tokenResponse . RefreshToken ) )
159205 {
@@ -226,6 +272,24 @@ public async Task<bool> HandleUnauthorizedResponseAsync(HttpResponseMessage resp
226272 _serverUrl ,
227273 cancellationToken ) ;
228274
275+ if ( resourceMetadata == null )
276+ {
277+ Console . WriteLine ( "Failed to extract resource metadata from response" ) ;
278+ return false ;
279+ }
280+
281+ // Cache the resource metadata for future use
282+ string requestUriKey = response . RequestMessage ? . RequestUri ? . ToString ( ) ?? string . Empty ;
283+ if ( ! string . IsNullOrEmpty ( requestUriKey ) )
284+ {
285+ _resourceMetadataCache [ requestUriKey ] = resourceMetadata ;
286+
287+ // Also cache with the canonical resource URI
288+ _resourceMetadataCache [ resourceMetadata . Resource . ToString ( ) ] = resourceMetadata ;
289+
290+ Console . WriteLine ( $ "Cached resource metadata for { requestUriKey } -> { resourceMetadata . Resource } ") ;
291+ }
292+
229293 // If we get here, the resource metadata is valid and matches our server
230294 Console . WriteLine ( $ "Successfully validated resource metadata for: { resourceMetadata . Resource } ") ;
231295
@@ -247,9 +311,11 @@ public async Task<bool> HandleUnauthorizedResponseAsync(HttpResponseMessage resp
247311
248312 if ( token != null )
249313 {
250- // Store the token in the cache
314+ // Store the token in the cache using the canonical resource URI from metadata
251315 string resourceKey = resourceMetadata . Resource . ToString ( ) ;
316+ Console . WriteLine ( $ "Storing token with canonical resource key: { resourceKey } ") ;
252317 _tokenCache [ resourceKey ] = token ;
318+
253319 Console . WriteLine ( "Successfully obtained a new token" ) ;
254320 return true ;
255321 }
@@ -543,15 +609,9 @@ private Uri BuildAuthorizationUrl(
543609
544610 if ( tokenResponse != null )
545611 {
546- // Set the time when the token was obtained
612+ // Set the time when the token was obtained - ExpiresAt will be calculated automatically
547613 tokenResponse . ObtainedAt = DateTimeOffset . UtcNow ;
548614
549- // Calculate expiration time if not set
550- if ( tokenResponse . ExpiresIn > 0 && tokenResponse . ExpiresAt == default )
551- {
552- tokenResponse . ExpiresAt = tokenResponse . ObtainedAt . AddSeconds ( tokenResponse . ExpiresIn ) ;
553- }
554-
555615 Console . WriteLine ( "Token exchange successful" ) ;
556616 return tokenResponse ;
557617 }
0 commit comments