4
4
* for more information concerning the license and the contributors participating to this project.
5
5
*/
6
6
7
+ using System ;
8
+ using System . Collections . Generic ;
9
+ using System . Net . Http ;
10
+ using System . Net . Http . Headers ;
7
11
using System . Security . Claims ;
12
+ using System . Text ;
8
13
using System . Text . Encodings . Web ;
14
+ using System . Text . Json ;
9
15
using System . Threading . Tasks ;
10
16
using JetBrains . Annotations ;
11
17
using Microsoft . AspNetCore . Authentication ;
@@ -26,6 +32,47 @@ public NotionAuthenticationHandler(
26
32
{
27
33
}
28
34
35
+ protected override async Task < OAuthTokenResponse > ExchangeCodeAsync ( OAuthCodeExchangeContext context )
36
+ {
37
+ var tokenRequestParameters = new Dictionary < string , string > ( )
38
+ {
39
+ [ "client_id" ] = Options . ClientId ,
40
+ [ "redirect_uri" ] = context . RedirectUri ,
41
+ [ "client_secret" ] = Options . ClientSecret ,
42
+ [ "code" ] = context . Code ,
43
+ [ "grant_type" ] = "authorization_code" ,
44
+ } ;
45
+
46
+ // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl
47
+ if ( context . Properties . Items . TryGetValue ( OAuthConstants . CodeVerifierKey , out var codeVerifier ) &&
48
+ ! string . IsNullOrEmpty ( codeVerifier ) )
49
+ {
50
+ tokenRequestParameters [ OAuthConstants . CodeVerifierKey ] = codeVerifier ;
51
+ context . Properties . Items . Remove ( OAuthConstants . CodeVerifierKey ) ;
52
+ }
53
+
54
+ using var requestContent = new FormUrlEncodedContent ( tokenRequestParameters ! ) ;
55
+
56
+ using var requestMessage = new HttpRequestMessage ( HttpMethod . Post , Options . TokenEndpoint ) ;
57
+ requestMessage . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
58
+
59
+ byte [ ] byteArray = Encoding . ASCII . GetBytes ( Options . ClientId + ":" + Options . ClientSecret ) ;
60
+ requestMessage . Headers . Authorization =
61
+ new AuthenticationHeaderValue ( "Basic" , Convert . ToBase64String ( byteArray ) ) ;
62
+ requestMessage . Content = requestContent ;
63
+ requestMessage . Version = Backchannel . DefaultRequestVersion ;
64
+
65
+ using var response = await Backchannel . SendAsync ( requestMessage , Context . RequestAborted ) ;
66
+ if ( response . IsSuccessStatusCode )
67
+ {
68
+ var payload = JsonDocument . Parse ( await response . Content . ReadAsStringAsync ( ) ) ;
69
+ return OAuthTokenResponse . Success ( payload ) ;
70
+ }
71
+
72
+ var error = "OAuth token endpoint failure: " + await DisplayAsync ( response ) ;
73
+ return OAuthTokenResponse . Failed ( new Exception ( error ) ) ;
74
+ }
75
+
29
76
protected override async Task < AuthenticationTicket > CreateTicketAsync (
30
77
[ NotNull ] ClaimsIdentity identity ,
31
78
[ NotNull ] AuthenticationProperties properties ,
@@ -46,5 +93,14 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
46
93
await Options . Events . CreatingTicket ( context ) ;
47
94
return new AuthenticationTicket ( context . Principal ! , context . Properties , Scheme . Name ) ;
48
95
}
96
+
97
+ private static async Task < string > DisplayAsync ( HttpResponseMessage response )
98
+ {
99
+ var output = new StringBuilder ( ) ;
100
+ output . Append ( "Status: " ) . Append ( response . StatusCode ) . Append ( ';' ) ;
101
+ output . Append ( "Headers: " ) . Append ( response . Headers ) . Append ( ';' ) ;
102
+ output . Append ( "Body: " ) . Append ( await response . Content . ReadAsStringAsync ( ) ) . Append ( ';' ) ;
103
+ return output . ToString ( ) ;
104
+ }
49
105
}
50
106
}
0 commit comments