@@ -158,53 +158,48 @@ public void ConfigureServices(IServiceCollection services)
158
158
options . TokenEndpoint = this . Configuration [ "DocuSign:TokenEndpoint" ] ;
159
159
options . UserInformationEndpoint = this . Configuration [ "DocuSign:UserInformationEndpoint" ] ;
160
160
161
- foreach ( var apiType in this . apiTypes )
162
- {
163
- foreach ( var scope in apiType . Value )
164
- {
165
- if ( ! options . Scope . Contains ( scope . ToLower ( ) ) )
166
- {
167
- options . Scope . Add ( scope ) ;
168
- }
169
- }
170
- }
161
+ string codeVerifier = GenerateCodeVerifier ( ) ;
162
+ string codeChallenge = GenerateCodeChallenge ( codeVerifier ) ;
171
163
172
- options . SaveTokens = true ;
173
- options . ClaimActions . MapJsonKey ( ClaimTypes . NameIdentifier , "sub" ) ;
174
- options . ClaimActions . MapJsonKey ( ClaimTypes . Name , "name" ) ;
175
- options . ClaimActions . MapJsonKey ( "accounts" , "accounts" ) ;
176
- options . ClaimActions . MapCustomJson ( "account_id" , obj => this . ExtractDefaultAccountValue ( obj , "account_id" ) ) ;
177
- options . ClaimActions . MapCustomJson ( "account_name" , obj => this . ExtractDefaultAccountValue ( obj , "account_name" ) ) ;
178
- options . ClaimActions . MapCustomJson ( "base_uri" , obj => this . ExtractDefaultAccountValue ( obj , "base_uri" ) ) ;
179
- options . ClaimActions . MapJsonKey ( "access_token" , "access_token" ) ;
180
- options . ClaimActions . MapJsonKey ( "refresh_token" , "refresh_token" ) ;
181
- options . ClaimActions . MapJsonKey ( "expires_in" , "expires_in" ) ;
182
164
options . Events = new OAuthEvents
183
165
{
184
166
OnRedirectToAuthorizationEndpoint = redirectContext =>
185
167
{
186
168
List < string > scopesForCurrentApi = this . apiTypes . GetValueOrDefault ( Enum . Parse < ExamplesApiType > ( this . Configuration [ "API" ] ) ) ;
187
169
188
- redirectContext . RedirectUri = this . UpdateRedirectUriScopes ( redirectContext . RedirectUri , scopesForCurrentApi ) ;
170
+ var redirectUri = this . UpdateRedirectUriScopes ( redirectContext . RedirectUri , scopesForCurrentApi ) ;
171
+
172
+ var pkceQuery = $ "&code_challenge={ codeChallenge } &code_challenge_method=S256";
173
+ redirectContext . RedirectUri = redirectUri + pkceQuery ;
174
+
175
+ redirectContext . HttpContext . Session . SetString ( "code_verifier" , codeVerifier ) ;
189
176
190
177
redirectContext . HttpContext . Response . Redirect ( redirectContext . RedirectUri ) ;
191
178
return Task . FromResult ( 0 ) ;
192
179
} ,
193
180
OnCreatingTicket = async context =>
194
181
{
195
- var request = new HttpRequestMessage ( HttpMethod . Get , context . Options . UserInformationEndpoint ) ;
196
- request . Headers . Accept . Add ( new MediaTypeWithQualityHeaderValue ( "application/json" ) ) ;
197
- request . Headers . Authorization = new AuthenticationHeaderValue ( "Bearer" , context . AccessToken ) ;
198
- var response = await context . Backchannel . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead , context . HttpContext . RequestAborted ) ;
199
- response . EnsureSuccessStatusCode ( ) ;
200
- var user = JObject . Parse ( await response . Content . ReadAsStringAsync ( ) ) ;
201
- user . Add ( "access_token" , context . AccessToken ) ;
202
- user . Add ( "refresh_token" , context . RefreshToken ) ;
203
- user . Add ( "expires_in" , DateTime . Now . Add ( context . ExpiresIn . Value ) . ToString ( ) ) ;
204
- using ( JsonDocument payload = JsonDocument . Parse ( user . ToString ( ) ) )
182
+ string codeVerifier = context . HttpContext . Session . GetString ( "code_verifier" ) ;
183
+
184
+ var tokenRequestParams = new Dictionary < string , string >
205
185
{
206
- context . RunClaimActions ( payload . RootElement ) ;
207
- }
186
+ { "grant_type" , "authorization_code" } ,
187
+ { "code" , context . ProtocolMessage . Code } ,
188
+ { "redirect_uri" , context . Properties . RedirectUri } ,
189
+ { "client_id" , options . ClientId } ,
190
+ { "code_verifier" , codeVerifier } ,
191
+ } ;
192
+
193
+ var requestContent = new FormUrlEncodedContent ( tokenRequestParams ) ;
194
+ var requestMessage = new HttpRequestMessage ( HttpMethod . Post , options . TokenEndpoint )
195
+ {
196
+ Content = requestContent
197
+ } ;
198
+ var response = await context . Backchannel . SendAsync ( requestMessage , HttpCompletionOption . ResponseHeadersRead , context . HttpContext . RequestAborted ) ;
199
+ response . EnsureSuccessStatusCode ( ) ;
200
+
201
+ var payload = JsonDocument . Parse ( await response . Content . ReadAsStringAsync ( ) ) ;
202
+ context . RunClaimActions ( payload . RootElement ) ;
208
203
} ,
209
204
OnRemoteFailure = context =>
210
205
{
@@ -266,6 +261,33 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
266
261
} ) ;
267
262
}
268
263
264
+ private string GenerateCodeVerifier ( )
265
+ {
266
+ using ( var rng = new RNGCryptoServiceProvider ( ) )
267
+ {
268
+ var bytes = new byte [ 32 ] ;
269
+ rng . GetBytes ( bytes ) ;
270
+ return Base64UrlEncode ( bytes ) ;
271
+ }
272
+ }
273
+
274
+ private string GenerateCodeChallenge ( string codeVerifier )
275
+ {
276
+ using ( var sha256 = SHA256 . Create ( ) )
277
+ {
278
+ var hash = sha256 . ComputeHash ( Encoding . UTF8 . GetBytes ( codeVerifier ) ) ;
279
+ return Base64UrlEncode ( hash ) ;
280
+ }
281
+ }
282
+
283
+ private string Base64UrlEncode ( byte [ ] input )
284
+ {
285
+ return Convert . ToBase64String ( input )
286
+ . Replace ( "+" , "-" )
287
+ . Replace ( "/" , "_" )
288
+ . Replace ( "=" , "" ) ;
289
+ }
290
+
269
291
private string UpdateRedirectUriScopes ( string uri , List < string > wantedScopes )
270
292
{
271
293
const string pattern = @"(?:&|\?)scope=([^&]+)" ;
0 commit comments