@@ -149,7 +149,7 @@ private Uri ExtractBaseResourceUri(Uri metadataUri)
149
149
/// <returns>The resource metadata if the resource matches the server, otherwise throws an exception.</returns>
150
150
/// <exception cref="InvalidOperationException">Thrown when the response is not a 401, lacks a WWW-Authenticate header,
151
151
/// lacks a resource_metadata parameter, the metadata can't be fetched, or the resource URI doesn't match the server URL.</exception>
152
- public async Task < ProtectedResourceMetadata > ExtractProtectedResourceMetadata (
152
+ internal async Task < ProtectedResourceMetadata > ExtractProtectedResourceMetadata (
153
153
HttpResponseMessage response ,
154
154
Uri serverUrl ,
155
155
CancellationToken cancellationToken = default )
@@ -169,7 +169,7 @@ public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
169
169
string ? resourceMetadataUrl = null ;
170
170
foreach ( var header in response . Headers . WwwAuthenticate )
171
171
{
172
- if ( string . Equals ( header . Scheme , "Bearer" , StringComparison . OrdinalIgnoreCase ) &&
172
+ if ( string . Equals ( header . Scheme , "Bearer" , StringComparison . OrdinalIgnoreCase ) &&
173
173
! string . IsNullOrEmpty ( header . Parameter ) )
174
174
{
175
175
resourceMetadataUrl = ParseWwwAuthenticateParameters ( header . Parameter , "resource_metadata" ) ;
@@ -186,17 +186,17 @@ public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
186
186
}
187
187
188
188
Uri metadataUri = new ( resourceMetadataUrl ) ;
189
-
189
+
190
190
var metadata = await FetchProtectedResourceMetadataAsync ( metadataUri , cancellationToken ) . ConfigureAwait ( false ) ;
191
191
if ( metadata == null )
192
192
{
193
193
throw new InvalidOperationException ( $ "Failed to fetch resource metadata from { resourceMetadataUrl } ") ;
194
194
}
195
-
195
+
196
196
// Extract the base URI from the metadata URL
197
197
Uri urlToValidate = ExtractBaseResourceUri ( metadataUri ) ;
198
198
_logger . LogDebug ( $ "Validating resource metadata against base URL: { urlToValidate } ") ;
199
-
199
+
200
200
if ( ! VerifyResourceMatch ( metadata , urlToValidate ) )
201
201
{
202
202
throw new InvalidOperationException (
@@ -245,5 +245,33 @@ public async Task<ProtectedResourceMetadata> ExtractProtectedResourceMetadata(
245
245
}
246
246
247
247
return null ;
248
+ } /// <summary>
249
+ /// Handles a 401 Unauthorized response and returns all available authorization servers.
250
+ /// This is the primary method for OAuth discovery - use this when you want full control
251
+ /// over authorization server selection.
252
+ /// </summary>
253
+ /// <param name="response">The 401 HTTP response.</param>
254
+ /// <param name="serverUrl">The server URL that returned the 401.</param>
255
+ /// <param name="cancellationToken">A token to cancel the operation.</param>
256
+ /// <returns>A list of available authorization server URIs.</returns>
257
+ /// <exception cref="ArgumentNullException">Thrown when response is null.</exception>
258
+ public async Task < IReadOnlyList < Uri > > GetAvailableAuthorizationServersAsync (
259
+ HttpResponseMessage response ,
260
+ Uri serverUrl ,
261
+ CancellationToken cancellationToken = default )
262
+ {
263
+ if ( response == null ) throw new ArgumentNullException ( nameof ( response ) ) ;
264
+
265
+ try
266
+ {
267
+ // Extract resource metadata behind the scenes
268
+ var metadata = await ExtractProtectedResourceMetadata ( response , serverUrl , cancellationToken ) ;
269
+ return metadata . AuthorizationServers ?? new List < Uri > ( ) ;
270
+ }
271
+ catch ( Exception ex )
272
+ {
273
+ _logger . LogError ( ex , "Failed to get available authorization servers" ) ;
274
+ return new List < Uri > ( ) ;
275
+ }
248
276
}
249
277
}
0 commit comments