Single sign on Xamarin Essentials无法为令牌交换Okta授权代码

Single sign on Xamarin Essentials无法为令牌交换Okta授权代码,single-sign-on,okta,pkce,xamarin.essentials,Single Sign On,Okta,Pkce,Xamarin.essentials,我使用的是OpenID,我们必须切换到Xamarin.Essentials.WebAuthenticator。 我可以使用WebAuthenticator.AuthenticateTasync()从Okta获得授权码。 但是,我尝试将代码转换为访问令牌的所有操作都会返回400个错误请求。 Okta的API错误是“E0000021:HTTP媒体类型不支持异常”,它接着说,“请求错误。接受和/或内容类型头可能与支持的值不匹配。” 我已经尽可能多地遵循了这一点,但我们并不像他那样使用混合补助金类型。

我使用的是OpenID,我们必须切换到Xamarin.Essentials.WebAuthenticator。
我可以使用WebAuthenticator.AuthenticateTasync()从Okta获得授权码。
但是,我尝试将代码转换为访问令牌的所有操作都会返回400个错误请求。
Okta的API错误是“E0000021:HTTP媒体类型不支持异常”,它接着说,“请求错误。接受和/或内容类型头可能与支持的值不匹配。”

我已经尽可能多地遵循了这一点,但我们并不像他那样使用混合补助金类型。
我们只使用授权码,这意味着我必须打第二个电话,我花了两天的时间试图弄清楚怎么做


private async Task LoginOktaAsync()
{
  try
  {
    var loginUrl = new Uri(BuildAuthenticationUrl());  // that method is down below
    var callbackUrl = new Uri("com.oktapreview.dev-999999:/callback"); // it's not really 999999
    var authenticationResult = await Xamarin.Essentials.WebAuthenticator.AuthenticateAsync(loginUrl, callbackUrl);

    string authCode;                                
    authenticationResult.Properties.TryGetValue("code",out authCode);

    // Everything works fine up to this point. I get the authorization code.

    var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/token"
         +"?grant_type=authorization_code"
         +$"&code={authCode}&client_id={OktaConfiguration.ClientId}&code_verifier={codeVerifier}";

    var request = new HttpRequestMessage(HttpMethod.Post, url);
    var client = new HttpClient();
    var response = await client.SendAsync(request); // this generates the 400 error.
  }
  catch(Exception e)
  {
    Debug.WriteLine($"Error: {e.Message}");
  }
}
以下是生成登录url的方法以及一些其他内容:


public string BuildAuthenticationUrl()
{
  var state = CreateCryptoGuid();
  var nonce = CreateCryptoGuid();

  CreateCodeChallenge();

  var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/authorize?response_type=code"
      + "&response_mode=fragment"
      + "&scope=openid%20profile%20email"
      + "&redirect_uri=com.oktapreview.dev-999999:/callback"
      +$"&client_id={OktaConfiguration.ClientId}"
      +$"&state={state}"
      +$"&code_challenge={codeChallenge}"
      + "&code_challenge_method=S256"
      +$"&nonce={nonce}";
  return url;
}

private string CreateCryptoGuid()
{
  using (var generator = RandomNumberGenerator.Create())
  {
    var bytes = new byte[16];
    generator.GetBytes(bytes);
    return new Guid(bytes).ToString("N");
  }
}

private string CreateCodeChallenge()
{
  codeChallenge = GenerateCodeToVerify();
  codeVerifier = codeChallenge;
  using (var sha256 = SHA256.Create())
  {
    var codeChallengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeChallenge));
    return Convert.ToBase64String(codeChallengeBytes);
  }
}    

private string GenerateCodeToVerify() 
{
  var str = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
  Random rnd = new Random();
  for (var i = 0; i < 100; i++) 
  {
    str += possible.Substring(rnd.Next(0,possible.Length-1),1);
  }
  return str;
}


'''


公共字符串BuildAuthenticationUrl()
{
var state=CreateCryptoGuid();
var nonce=CreateCryptoGuid();
CreateCodeChallenge();
var url=$”https://dev-999999.oktapreview.com/oauth2/default/v1/authorize?response_type=code"
+“&response\u mode=fragment”
+“&scope=openid%20配置文件%20电子邮件”
+“&redirect_uri=com.oktapreview.dev-99999:/callback”
+$“&client_id={OktaConfiguration.ClientId}”
+$“&state={state}”
+$“&code_challenge={codeChallenge}”
+“&code\u challenge\u method=S256”
+$“&nonce={nonce}”;
返回url;
}
私有字符串CreateCryptoGuid()
{
使用(var生成器=RandomNumberGenerator.Create())
{
var字节=新字节[16];
生成器.GetBytes(字节);
返回新的Guid(字节).ToString(“N”);
}
}
私有字符串CreateCodecChallenge()
{
codeChallenge=GenerateCodeToVerify();
codeVerifier=codeChallenge;
使用(var sha256=sha256.Create())
{
var codecchallengebytes=sha256.ComputeHash(Encoding.UTF8.GetBytes(codecchallenge));
返回Convert.ToBase64String(codeChallengeBytes);
}
}    
私有字符串GenerateCodeToVerify()
{
var str=“”;
var PROBIBLE=“ABCDEFGHIJKLMNOPQRSTUVXYZABCDFGHIJKLMNOPQRSTUVXYZ0123456789-。”;
随机rnd=新随机();
对于(变量i=0;i<100;i++)
{
str+=可能的.Substring(rnd.Next(0,可能的.Length-1),1);
}
返回str;
}
'''

在进行了大量的在线调查之后,我发现问题在于我是如何在帖子中获取代币的。我就是这样做的:

public static Dictionary<string, string> JsonDecode(string encodedString)
{
    var inputs = new Dictionary<string, string>();
    var json = JValue.Parse(encodedString) as JObject;

    foreach (KeyValuePair<string, JToken> kv in json)
    {
        if (kv.Value is JValue v)
        {
            if (v.Type != JTokenType.String)
                inputs[kv.Key] = v.ToString();
            else
                inputs[kv.Key] = (string)v;
        }
    }
    return inputs;
}


private async Task<string> ExchangeAuthCodeForToken(string authCode)
{
    string accessToken = string.Empty;
    List<KeyValuePair<string, string>> kvdata = new List<KeyValuePair<string, string>>
    {
        new KeyValuePair<string, string>("grant_type", "authorization_code"),
        new KeyValuePair<string, string>("code", authCode),
        new KeyValuePair<string, string>("redirect_uri", OktaConfiguration.Callback),
        new KeyValuePair<string, string>("client_id", OktaConfiguration.ClientId),
        new KeyValuePair<string, string>("code_verifier", codeVerifier)
    };
    var content = new FormUrlEncodedContent(kvdata);

    var request = new HttpRequestMessage(HttpMethod.Post, OktaConfiguration.TokenUrl)
                {Content = content, Method = HttpMethod.Post};
    HttpClient client = new HttpClient();
    HttpResponseMessage response = await client.SendAsync(request);
    string text = await response.Content.ReadAsStringAsync();
    Dictionary<string, string> data = JsonDecode(text);
    data.TryGetValue("access_token", out accessToken);
    return accessToken;
}
公共静态字典JsonDecode(字符串编码字符串)
{
var输入=新字典();
var json=JValue.Parse(encodedString)作为JObject;
foreach(json中的KeyValuePair)
{
如果(千伏值为千伏值)
{
if(v.Type!=JTokenType.String)
输入[kv.键]=v.ToString();
其他的
输入[kv.键]=(串)v;
}
}
返回输入;
}
专用异步任务ExchangeAuthCodeForToken(字符串authCode)
{
string accessToken=string.Empty;
List kvdata=新列表
{
新的KeyValuePair(“授权类型”、“授权代码”),
新的KeyValuePair(“代码”,authCode),
新的KeyValuePair(“重定向uri”,OktaConfiguration.Callback),
新的KeyValuePair(“客户端id”,OktaConfiguration.ClientId),
新的KeyValuePair(“代码验证器”,代码验证器)
};
var content=新的FormUrlEncodedContent(kvdata);
var request=newhttprequestmessage(HttpMethod.Post,OktaConfiguration.TokenUrl)
{Content=Content,Method=HttpMethod.Post};
HttpClient=新的HttpClient();
HttpResponseMessage response=等待客户端.SendAsync(请求);
string text=wait response.Content.ReadAsStringAsync();
字典数据=JsonDecode(文本);
data.TryGetValue(“访问令牌”,out accessToken);
返回accessToken;
}