Single sign on Xamarin Essentials无法为令牌交换Okta授权代码
我使用的是OpenID,我们必须切换到Xamarin.Essentials.WebAuthenticator。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媒体类型不支持异常”,它接着说,“请求错误。接受和/或内容类型头可能与支持的值不匹配。” 我已经尽可能多地遵循了这一点,但我们并不像他那样使用混合补助金类型。
我可以使用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;
}