@@ -9,8 +9,6 @@ namespace ModelContextProtocol.Authentication;
99/// </summary>
1010internal sealed class AuthenticatingMcpHttpClient ( HttpClient httpClient , IMcpCredentialProvider credentialProvider ) : McpHttpClient ( httpClient )
1111{
12- private static readonly char [ ] SchemeSplitDelimiters = [ ' ' , ',' ] ;
13-
1412 // Select first supported scheme as the default
1513 private string _currentScheme = credentialProvider . SupportedSchemes . FirstOrDefault ( ) ??
1614 throw new ArgumentException ( "Authorization provider must support at least one authentication scheme." , nameof ( credentialProvider ) ) ;
@@ -47,88 +45,41 @@ private async Task<HttpResponseMessage> HandleUnauthorizedResponseAsync(
4745 // Gather the schemes the server wants us to use from WWW-Authenticate headers
4846 var serverSchemes = ExtractServerSupportedSchemes ( response ) ;
4947
50- // Find the intersection between what the server supports and what our provider supports
51- string ? bestSchemeMatch = null ;
52-
53- // First try to find a direct match with the current scheme if it's still valid
54- string schemeUsed = originalRequest . Headers . Authorization ? . Scheme ?? _currentScheme ?? string . Empty ;
55- if ( ! string . IsNullOrEmpty ( schemeUsed ) &&
56- serverSchemes . Contains ( schemeUsed ) &&
57- credentialProvider . SupportedSchemes . Contains ( schemeUsed ) )
58- {
59- bestSchemeMatch = schemeUsed ;
60- }
61- else
48+ if ( ! serverSchemes . Contains ( _currentScheme ) )
6249 {
6350 // Find the first server scheme that's in our supported set
64- bestSchemeMatch = serverSchemes . Intersect ( credentialProvider . SupportedSchemes , StringComparer . OrdinalIgnoreCase ) . FirstOrDefault ( ) ;
65-
66- // If no match was found, either throw an exception or use default
67- if ( bestSchemeMatch is null )
68- {
69- if ( serverSchemes . Count > 0 )
70- {
71- throw new IOException (
72- $ "The server does not support any of the provided authentication schemes." +
73- $ "Server supports: [{ string . Join ( ", " , serverSchemes ) } ], " +
74- $ "Provider supports: [{ string . Join ( ", " , credentialProvider . SupportedSchemes ) } ].") ;
75- }
76-
77- // If the server didn't specify any schemes, use the provider's default
78- bestSchemeMatch = credentialProvider . SupportedSchemes . FirstOrDefault ( ) ;
79- }
80- }
51+ var bestSchemeMatch = serverSchemes . Intersect ( credentialProvider . SupportedSchemes , StringComparer . OrdinalIgnoreCase ) . FirstOrDefault ( ) ;
8152
82- if ( bestSchemeMatch != null )
83- {
84- try
53+ if ( bestSchemeMatch is not null )
8554 {
86- // Try to handle the 401 response with the selected scheme
87- var ( handled , recommendedScheme ) = await credentialProvider . HandleUnauthorizedResponseAsync (
88- response ,
89- bestSchemeMatch ,
90- cancellationToken ) . ConfigureAwait ( false ) ;
91-
92- if ( ! handled )
93- {
94- throw new McpException (
95- $ "Failed to handle unauthorized response with scheme '{ bestSchemeMatch } '. " +
96- "The authentication provider was unable to process the authentication challenge." ) ;
97- }
98-
99- _currentScheme = recommendedScheme ?? bestSchemeMatch ;
55+ _currentScheme = bestSchemeMatch ;
10056 }
101- catch ( Exception ex )
57+ else if ( serverSchemes . Count > 0 )
10258 {
59+ // If no match was found, either throw an exception or use default
10360 throw new McpException (
104- $ "Failed to handle unauthorized response with scheme ' { bestSchemeMatch } '. " +
105- "The authentication provider encountered an error while processing the authentication challenge." ,
106- ex ) ;
61+ $ "The server does not support any of the provided authentication schemes. " +
62+ $ "Server supports: [ { string . Join ( ", " , serverSchemes ) } ], " +
63+ $ "Provider supports: [ { string . Join ( ", " , credentialProvider . SupportedSchemes ) } ]." ) ;
10764 }
65+ }
10866
109- var retryRequest = new HttpRequestMessage ( originalRequest . Method , originalRequest . RequestUri )
110- {
111- Version = originalRequest . Version ,
112- #if NET
113- VersionPolicy = originalRequest . VersionPolicy ,
114- #endif
115- } ;
116-
117- // Copy headers except Authorization which we'll set separately
118- foreach ( var header in originalRequest . Headers )
119- {
120- if ( ! header . Key . Equals ( "Authorization" , StringComparison . OrdinalIgnoreCase ) )
121- {
122- retryRequest . Headers . TryAddWithoutValidation ( header . Key , header . Value ) ;
123- }
124- }
67+ // Try to handle the 401 response with the selected scheme
68+ await credentialProvider . HandleUnauthorizedResponseAsync ( _currentScheme , response , cancellationToken ) . ConfigureAwait ( false ) ;
12569
126- await AddAuthorizationHeaderAsync ( retryRequest , _currentScheme , cancellationToken ) . ConfigureAwait ( false ) ;
70+ using var retryRequest = new HttpRequestMessage ( originalRequest . Method , originalRequest . RequestUri ) ;
12771
128- return await base . SendAsync ( retryRequest , originalJsonRpcMessage , cancellationToken ) . ConfigureAwait ( false ) ;
72+ // Copy headers except Authorization which we'll set separately
73+ foreach ( var header in originalRequest . Headers )
74+ {
75+ if ( ! header . Key . Equals ( "Authorization" , StringComparison . OrdinalIgnoreCase ) )
76+ {
77+ retryRequest . Headers . TryAddWithoutValidation ( header . Key , header . Value ) ;
78+ }
12979 }
13080
131- return response ; // Return the original response if we couldn't handle it
81+ await AddAuthorizationHeaderAsync ( retryRequest , _currentScheme , cancellationToken ) . ConfigureAwait ( false ) ;
82+ return await base . SendAsync ( retryRequest , originalJsonRpcMessage , cancellationToken ) . ConfigureAwait ( false ) ;
13283 }
13384
13485 /// <summary>
@@ -138,15 +89,9 @@ private static HashSet<string> ExtractServerSupportedSchemes(HttpResponseMessage
13889 {
13990 var serverSchemes = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
14091
141- if ( response . Headers . Contains ( "WWW-Authenticate" ) )
92+ foreach ( var header in response . Headers . WwwAuthenticate )
14293 {
143- foreach ( var authHeader in response . Headers . GetValues ( "WWW-Authenticate" ) )
144- {
145- // Extract the scheme from the WWW-Authenticate header
146- // Format is typically: "Scheme param1=value1, param2=value2"
147- string scheme = authHeader . Split ( SchemeSplitDelimiters , StringSplitOptions . RemoveEmptyEntries ) [ 0 ] ;
148- serverSchemes . Add ( scheme ) ;
149- }
94+ serverSchemes . Add ( header . Scheme ) ;
15095 }
15196
15297 return serverSchemes ;
@@ -157,13 +102,17 @@ private static HashSet<string> ExtractServerSupportedSchemes(HttpResponseMessage
157102 /// </summary>
158103 private async Task AddAuthorizationHeaderAsync ( HttpRequestMessage request , string scheme , CancellationToken cancellationToken )
159104 {
160- if ( request . RequestUri != null )
105+ if ( request . RequestUri is null )
161106 {
162- var token = await credentialProvider . GetCredentialAsync ( scheme , request . RequestUri , cancellationToken ) . ConfigureAwait ( false ) ;
163- if ( ! string . IsNullOrEmpty ( token ) )
164- {
165- request . Headers . Authorization = new AuthenticationHeaderValue ( scheme , token ) ;
166- }
107+ return ;
108+ }
109+
110+ var token = await credentialProvider . GetCredentialAsync ( scheme , request . RequestUri , cancellationToken ) . ConfigureAwait ( false ) ;
111+ if ( string . IsNullOrEmpty ( token ) )
112+ {
113+ return ;
167114 }
115+
116+ request . Headers . Authorization = new AuthenticationHeaderValue ( scheme , token ) ;
168117 }
169118}
0 commit comments