var auth = new OAuth2Authenticator(clientId: "my client id",
scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/calendar",
authorizeUrl: new Uri("https://accounts.google.com/o/oauth2/auth"),
redirectUrl: new Uri("https://www.googleapis.com/plus/v1/people/me"),
getUsernameAsync: null);
auth.AllowCancel = allowCancel;
List<Event> events = new List<Event>();
auth.Completed += async (sender, e) =>
{
if (!e.IsAuthenticated)
{
Toast.MakeText(_activity, "Fail to authenticate!", ToastLength.Short).Show();
return;
}
string access_token;
e.Account.Properties.TryGetValue("access_token", out access_token);
当我尝试以相同的方式检索刷新令牌时,我检索访问令牌它不起作用。
我能够获得访问令牌但无法获得刷新令牌。我尝试从谷歌帐户删除授权,因为我读到你第一次授予访问权限时只获得刷新令牌,但我仍然无法检索访问令牌。我还读过我需要在请求中添加access_type = offline和approval_prompt = force,但我不知道在哪里使用xamarin.auth添加它。
答案 0 :(得分:0)
在VS中搜索时获得的默认包是&#39; Xamarin Auth 1.3.0&#39;它不支持获取刷新令牌。
我已经通过制作我自己的OAuth2Authenticator
版本来解决这个问题,并且付出了非常有限的努力达到了同样的目标。
public class RefreshOAuth2Authenticator : WebRedirectAuthenticator
{
string clientId;
string clientSecret;
string scope;
Uri authorizeUrl;
Uri accessTokenUrl;
Uri redirectUrl;
GetUsernameAsyncFunc getUsernameAsync;
string requestState;
bool reportedForgery;
public string ClientId
{
get { return clientId; }
}
public string ClientSecret
{
get { return clientSecret; }
}
public string Scope
{
get { return scope; }
}
public Uri AuthorizeUrl
{
get { return authorizeUrl; }
}
public Uri RedirectUrl
{
get { return redirectUrl; }
}
public Uri AccessTokenUrl
{
get { return accessTokenUrl; }
}
public RefreshOAuth2Authenticator(string clientId, string scope, Uri authorizeUrl, Uri redirectUrl, GetUsernameAsyncFunc getUsernameAsync = null)
: this (redirectUrl)
{
if (string.IsNullOrEmpty(clientId))
{
throw new ArgumentException("clientId must be provided", nameof(clientId));
}
this.clientId = clientId;
this.scope = scope ?? "";
if (authorizeUrl == null)
{
throw new ArgumentNullException(nameof(authorizeUrl));
}
this.authorizeUrl = authorizeUrl;
this.getUsernameAsync = getUsernameAsync;
this.redirectUrl = redirectUrl;
accessTokenUrl = null;
}
public RefreshOAuth2Authenticator(string clientId, string clientSecret, string scope, Uri authorizeUrl, Uri redirectUrl, Uri accessTokenUrl, GetUsernameAsyncFunc getUsernameAsync = null)
: this (redirectUrl, clientSecret, accessTokenUrl)
{
if (string.IsNullOrEmpty(clientId))
{
throw new ArgumentException("clientId must be provided", nameof(clientId));
}
this.clientId = clientId;
if (string.IsNullOrEmpty(clientSecret))
{
throw new ArgumentException("clientSecret must be provided", nameof(clientSecret));
}
this.clientSecret = clientSecret;
this.scope = scope ?? "";
if (authorizeUrl == null)
{
throw new ArgumentNullException(nameof(authorizeUrl));
}
this.authorizeUrl = authorizeUrl;
if (accessTokenUrl == null)
{
throw new ArgumentNullException(nameof(accessTokenUrl));
}
this.accessTokenUrl = accessTokenUrl;
this.redirectUrl = redirectUrl;
this.getUsernameAsync = getUsernameAsync;
}
RefreshOAuth2Authenticator(Uri redirectUrl, string clientSecret = null, Uri accessTokenUrl = null)
: base (redirectUrl, redirectUrl)
{
this.clientSecret = clientSecret;
this.accessTokenUrl = accessTokenUrl;
this.redirectUrl = redirectUrl;
//
// Generate a unique state string to check for forgeries
//
var chars = new char[16];
var rand = new Random();
for (var i = 0; i < chars.Length; i++)
{
chars[i] = (char)rand.Next('a', 'z' + 1);
}
requestState = new string(chars);
}
bool IsImplicit { get { return accessTokenUrl == null; } }
public override Task<Uri> GetInitialUrlAsync()
{
var url = new Uri(string.Format(
"{0}?client_id={1}&redirect_uri={2}&response_type={3}&scope={4}&state={5}&access_type=offline&prompt=consent",
authorizeUrl.AbsoluteUri,
Uri.EscapeDataString(clientId),
Uri.EscapeDataString(RedirectUrl.AbsoluteUri),
IsImplicit ? "token" : "code",
Uri.EscapeDataString(scope),
Uri.EscapeDataString(requestState)));
var tcs = new TaskCompletionSource<Uri>();
tcs.SetResult(url);
return tcs.Task;
}
public async virtual Task<IDictionary<string, string>> RequestRefreshTokenAsync(string refreshToken)
{
var queryValues = new Dictionary<string, string>
{
{"refresh_token", refreshToken},
{"client_id", this.ClientId},
{"grant_type", "refresh_token"}
};
if (!string.IsNullOrEmpty(this.ClientSecret))
{
queryValues["client_secret"] = this.ClientSecret;
}
try
{
var accountProperties = await RequestAccessTokenAsync(queryValues).ConfigureAwait(false);
this.OnRetrievedAccountProperties(accountProperties);
return accountProperties;
}
catch (Exception e)
{
OnError(e);
throw; // maybe don't need this? this will throw the exception in order to maintain backward compatibility, but maybe could just return -1 or something instead?
}
}
protected override void OnPageEncountered(Uri url, IDictionary<string, string> query, IDictionary<string, string> fragment)
{
var all = new Dictionary<string, string>(query);
foreach (var kv in fragment)
all[kv.Key] = kv.Value;
//
// Check for forgeries
//
if (all.ContainsKey("state"))
{
if (all["state"] != requestState && !reportedForgery)
{
reportedForgery = true;
OnError("Invalid state from server. Possible forgery!");
return;
}
}
//
// Continue processing
//
base.OnPageEncountered(url, query, fragment);
}
protected override void OnRedirectPageLoaded(Uri url, IDictionary<string, string> query, IDictionary<string, string> fragment)
{
//
// Look for the access_token
//
if (fragment.ContainsKey("access_token"))
{
//
// We found an access_token
//
OnRetrievedAccountProperties(fragment);
}
else if (!IsImplicit)
{
//
// Look for the code
//
if (query.ContainsKey("code"))
{
var code = query["code"];
RequestAccessTokenAsync(code).ContinueWith(task =>
{
if (task.IsFaulted)
{
OnError(task.Exception);
}
else {
OnRetrievedAccountProperties(task.Result);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else {
OnError("Expected code in response, but did not receive one.");
return;
}
}
else {
OnError("Expected access_token in response, but did not receive one.");
return;
}
}
Task<IDictionary<string, string>> RequestAccessTokenAsync(string code)
{
var queryValues = new Dictionary<string, string> {
{ "grant_type", "authorization_code" },
{ "code", code },
{ "redirect_uri", RedirectUrl.AbsoluteUri },
{ "client_id", clientId },
};
if (!string.IsNullOrEmpty(clientSecret))
{
queryValues["client_secret"] = clientSecret;
}
return RequestAccessTokenAsync(queryValues);
}
protected Task<IDictionary<string, string>> RequestAccessTokenAsync(IDictionary<string, string> queryValues)
{
var query = queryValues.FormEncode();
var req = WebRequest.Create(accessTokenUrl);
req.Method = "POST";
var body = Encoding.UTF8.GetBytes(query);
req.ContentLength = body.Length;
req.ContentType = "application/x-www-form-urlencoded";
using (var s = req.GetRequestStream())
{
s.Write(body, 0, body.Length);
}
return req.GetResponseAsync().ContinueWith(task =>
{
var text = task.Result.GetResponseText();
// Parse the response
var data = text.Contains("{") ? WebEx.JsonDecode(text) : WebEx.FormDecode(text);
if (data.ContainsKey("error"))
{
throw new AuthException("Error authenticating: " + data["error"]);
}
else if (data.ContainsKey("access_token"))
{
return data;
}
else {
throw new AuthException("Expected access_token in access token response, but did not receive one.");
}
});
}
protected virtual void OnRetrievedAccountProperties(IDictionary<string, string> accountProperties)
{
//
// Now we just need a username for the account
//
if (getUsernameAsync != null)
{
getUsernameAsync(accountProperties).ContinueWith(task =>
{
if (task.IsFaulted)
{
OnError(task.Exception);
}
else {
OnSucceeded(task.Result, accountProperties);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else {
OnSucceeded("", accountProperties);
}
}
}
从LoginPageRenderer
开始,如下所示:
var auth = new RefreshOAuth2Authenticator(
clientId: "client ID",
clientSecret: "client Secret",
scope: "scope", // just 'openid' in my case
authorizeUrl: new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
redirectUrl: new Uri("your redirect url"),
accessTokenUrl: new Uri("https://www.googleapis.com/oauth2/v4/token") );
auth.GetUI();
此调用会产生一个带有刷新令牌的AccountStore,应该安全地保存。
每次我想获得一个新的id_token,我只需要打电话:
await auth.RequestRefreshTokenAsync("Refresh Token")
你需要更多关于检查expire_ts和存储令牌值的逻辑,但是上面的代码应该让你去。
注1:Xamarin.Auth的分支现在支持刷新令牌 https://github.com/xamarin/Xamarin.Auth/tree/portable-bait-and-switch我没有尝试过。
注意2:我知道客户端密钥存储在应用程序中,出于安全原因,建议不要这样做。但是,由于Google不支持使用已安装的凭据,因此我目前无法看到解决此问题的方法。