@@ -416,4 +416,82 @@ private async Task<OAuthToken> ExchangeAuthorizationCodeForTokenAsync(
416416
417417        return  tokenResponse ; 
418418    } 
419+ 
420+     /// <summary> 
421+     /// Refreshes an OAuth access token using a refresh token. 
422+     /// </summary> 
423+     /// <param name="tokenEndpoint">The token endpoint URI from the authorization server metadata.</param> 
424+     /// <param name="clientId">The client ID to use for authentication.</param> 
425+     /// <param name="clientSecret">The client secret to use for authentication, if available.</param> 
426+     /// <param name="refreshToken">The refresh token to use for obtaining a new access token.</param> 
427+     /// <param name="scopes">Optional scopes to request. If not provided, the server will use the same scopes as the original token.</param> 
428+     /// <returns>A new OAuth token response containing a new access token and potentially a new refresh token.</returns> 
429+     /// <exception cref="ArgumentNullException">Thrown when required parameters are null.</exception> 
430+     /// <exception cref="InvalidOperationException">Thrown when the token refresh fails.</exception> 
431+     public  async  Task < OAuthToken >  RefreshAccessTokenAsync ( 
432+         Uri  tokenEndpoint , 
433+         string  clientId , 
434+         string ?  clientSecret , 
435+         string  refreshToken , 
436+         IEnumerable < string > ?  scopes  =  null ) 
437+     { 
438+         if  ( tokenEndpoint  ==  null )  throw  new  ArgumentNullException ( nameof ( tokenEndpoint ) ) ; 
439+         if  ( string . IsNullOrEmpty ( clientId ) )  throw  new  ArgumentNullException ( nameof ( clientId ) ) ; 
440+         if  ( string . IsNullOrEmpty ( refreshToken ) )  throw  new  ArgumentNullException ( nameof ( refreshToken ) ) ; 
441+ 
442+         var  tokenRequest  =  new  Dictionary < string ,  string > 
443+         { 
444+             [ "grant_type" ]  =  "refresh_token" , 
445+             [ "refresh_token" ]  =  refreshToken , 
446+             [ "client_id" ]  =  clientId 
447+         } ; 
448+ 
449+         // Add scopes if provided 
450+         if  ( scopes  !=  null ) 
451+         { 
452+             tokenRequest [ "scope" ]  =  string . Join ( " " ,  scopes ) ; 
453+         } 
454+ 
455+         var  requestContent  =  new  FormUrlEncodedContent ( tokenRequest ) ; 
456+         
457+         HttpResponseMessage  response ; 
458+         if  ( ! string . IsNullOrEmpty ( clientSecret ) ) 
459+         { 
460+             // Add client authentication if secret is available 
461+             var  authValue  =  Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( $ "{ clientId } :{ clientSecret } ") ) ; 
462+             _httpClient . DefaultRequestHeaders . Authorization  =  new  AuthenticationHeaderValue ( "Basic" ,  authValue ) ; 
463+             response  =  await  _httpClient . PostAsync ( tokenEndpoint ,  requestContent ) ; 
464+             _httpClient . DefaultRequestHeaders . Authorization  =  null ; 
465+         } 
466+         else 
467+         { 
468+             response  =  await  _httpClient . PostAsync ( tokenEndpoint ,  requestContent ) ; 
469+         } 
470+ 
471+         try 
472+         { 
473+             response . EnsureSuccessStatusCode ( ) ; 
474+             
475+             var  json  =  await  response . Content . ReadAsStringAsync ( ) ; 
476+             var  tokenResponse  =  JsonSerializer . Deserialize ( json ,  McpJsonUtilities . DefaultOptions . GetTypeInfo < OAuthToken > ( ) ) ; 
477+             if  ( tokenResponse  ==  null ) 
478+             { 
479+                 throw  new  InvalidOperationException ( "Failed to parse token response." ) ; 
480+             } 
481+             
482+             // Some authorization servers might not return a new refresh token 
483+             // If no new refresh token is provided, keep the old one 
484+             if  ( string . IsNullOrEmpty ( tokenResponse . RefreshToken ) ) 
485+             { 
486+                 tokenResponse . RefreshToken  =  refreshToken ; 
487+             } 
488+             
489+             return  tokenResponse ; 
490+         } 
491+         catch  ( HttpRequestException  ex ) 
492+         { 
493+             string  errorContent  =  await  response . Content . ReadAsStringAsync ( ) ; 
494+             throw  new  InvalidOperationException ( $ "Failed to refresh access token: { ex . Message } . Response: { errorContent } ",  ex ) ; 
495+         } 
496+     } 
419497} 
0 commit comments