protected override async Task <OAuthTokenResponse> ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context) { // See https://opendocs.alipay.com/apis/api_9/alipay.system.oauth.token for details. var tokenRequestParameters = new SortedDictionary <string, string?>() { ["app_id"] = Options.ClientId, ["charset"] = "utf-8", ["code"] = context.Code, ["format"] = "JSON", ["grant_type"] = "authorization_code", ["method"] = "alipay.system.oauth.token", ["sign_type"] = "RSA2", ["timestamp"] = Clock.UtcNow.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), ["version"] = "1.0", }; tokenRequestParameters.Add("sign", GetRSA2Signature(tokenRequestParameters)); // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl if (context.Properties.Items.TryGetValue(OAuthConstants.CodeVerifierKey, out var codeVerifier)) { tokenRequestParameters.Add(OAuthConstants.CodeVerifierKey, codeVerifier); context.Properties.Items.Remove(OAuthConstants.CodeVerifierKey); } var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, tokenRequestParameters); using var response = await Backchannel.GetAsync(address, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted); if (!response.IsSuccessStatusCode) { await Log.AccessTokenError(Logger, response, Context.RequestAborted); return(OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token."))); } using var stream = await response.Content.ReadAsStreamAsync(Context.RequestAborted); using var document = JsonDocument.Parse(stream); var mainElement = document.RootElement.GetProperty("alipay_system_oauth_token_response"); if (!ValidateReturnCode(mainElement, out var code)) { return(OAuthTokenResponse.Failed(new Exception($"An error (Code:{code}) occurred while retrieving an access token."))); } var payload = JsonDocument.Parse(mainElement.GetRawText()); return(OAuthTokenResponse.Success(payload)); }