66
77using System . Net . Http . Headers ;
88using System . Security . Claims ;
9+ using System . Text ;
910using System . Text . Encodings . Web ;
1011using System . Text . Json ;
1112using Microsoft . Extensions . Logging ;
@@ -50,6 +51,41 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
5051 return new AuthenticationTicket ( context . Principal ! , context . Properties , Scheme . Name ) ;
5152 }
5253
54+ protected override async Task < OAuthTokenResponse > ExchangeCodeAsync ( [ NotNull ] OAuthCodeExchangeContext context )
55+ {
56+ var tokenRequestParameters = new Dictionary < string , string >
57+ {
58+ [ "redirect_uri" ] = context . RedirectUri ,
59+ [ "code" ] = context . Code ,
60+ [ "grant_type" ] = "authorization_code"
61+ } ;
62+
63+ // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl
64+ if ( context . Properties . Items . TryGetValue ( OAuthConstants . CodeVerifierKey , out var codeVerifier ) )
65+ {
66+ tokenRequestParameters [ OAuthConstants . CodeVerifierKey ] = codeVerifier ! ;
67+ context . Properties . Items . Remove ( OAuthConstants . CodeVerifierKey ) ;
68+ }
69+
70+ using var requestContent = new FormUrlEncodedContent ( tokenRequestParameters ! ) ;
71+ using var requestMessage = new HttpRequestMessage ( HttpMethod . Post , Options . TokenEndpoint ) ;
72+ requestMessage . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
73+ var credentials = Convert . ToBase64String ( Encoding . ASCII . GetBytes (
74+ $ "{ Options . ClientId } :{ Options . ClientSecret } ") ) ;
75+ requestMessage . Headers . Authorization = new AuthenticationHeaderValue ( "Basic" , credentials ) ;
76+ requestMessage . Content = requestContent ;
77+ requestMessage . Version = Backchannel . DefaultRequestVersion ;
78+ using var response = await Backchannel . SendAsync ( requestMessage , Context . RequestAborted ) ;
79+ if ( ! response . IsSuccessStatusCode )
80+ {
81+ await Log . ExchangeCodeAsync ( Logger , response , Context . RequestAborted ) ;
82+ return OAuthTokenResponse . Failed ( new Exception ( "An error occurred while retrieving an access token." ) ) ;
83+ }
84+
85+ var body = await response . Content . ReadAsStringAsync ( Context . RequestAborted ) ;
86+ return OAuthTokenResponse . Success ( JsonDocument . Parse ( body ) ) ;
87+ }
88+
5389 private static partial class Log
5490 {
5591 internal static async Task UserProfileErrorAsync ( ILogger logger , HttpResponseMessage response , CancellationToken cancellationToken )
@@ -67,5 +103,21 @@ private static partial void UserProfileError(
67103 System . Net . HttpStatusCode status ,
68104 string headers ,
69105 string body ) ;
106+
107+ internal static async Task ExchangeCodeAsync ( ILogger logger , HttpResponseMessage response , CancellationToken cancellationToken )
108+ {
109+ ExchangeCodeAsync (
110+ logger ,
111+ response . StatusCode ,
112+ response . Headers . ToString ( ) ,
113+ await response . Content . ReadAsStringAsync ( cancellationToken ) ) ;
114+ }
115+
116+ [ LoggerMessage ( 2 , LogLevel . Error , "An error occurred while retrieving an access token: the remote server returned a {Status} response with the following payload: {Headers} {Body}." ) ]
117+ static partial void ExchangeCodeAsync (
118+ ILogger logger ,
119+ System . Net . HttpStatusCode status ,
120+ string headers ,
121+ string body ) ;
70122 }
71123}
0 commit comments