@@ -158,53 +158,48 @@ public void ConfigureServices(IServiceCollection services)
158158 options . TokenEndpoint = this . Configuration [ "DocuSign:TokenEndpoint" ] ;
159159 options . UserInformationEndpoint = this . Configuration [ "DocuSign:UserInformationEndpoint" ] ;
160160
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 ) ;
171163
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" ) ;
182164 options . Events = new OAuthEvents
183165 {
184166 OnRedirectToAuthorizationEndpoint = redirectContext =>
185167 {
186168 List < string > scopesForCurrentApi = this . apiTypes . GetValueOrDefault ( Enum . Parse < ExamplesApiType > ( this . Configuration [ "API" ] ) ) ;
187169
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 ) ;
189176
190177 redirectContext . HttpContext . Response . Redirect ( redirectContext . RedirectUri ) ;
191178 return Task . FromResult ( 0 ) ;
192179 } ,
193180 OnCreatingTicket = async context =>
194181 {
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 >
205185 {
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 ) ;
208203 } ,
209204 OnRemoteFailure = context =>
210205 {
@@ -266,6 +261,33 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
266261 } ) ;
267262 }
268263
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+
269291 private string UpdateRedirectUriScopes ( string uri , List < string > wantedScopes )
270292 {
271293 const string pattern = @"(?:&|\?)scope=([^&]+)" ;
0 commit comments