2016-07-07 6 views
3
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); 

액세스 토큰을 검색하는 것과 동일한 방법으로 새로 고침 토큰을 검색하려고하면 작동하지 않습니다.xamarin.auth를 사용하여 Google API의 새로 고침 토큰을 받으십시오.

액세스 토큰을 가져올 수 있지만 새로 고침 토큰을 가져올 수 없습니다. 처음으로 액세스 권한을 부여 할 때 새로 고침 토큰 만 가져 오지만 여전히 액세스 토큰을 검색 할 수는 없으므로 Google 계정에서 승인을 삭제하려고했습니다. 또한 access_type = offline 및 approval_prompt = force를 요청에 추가해야하지만 xamarin.auth를 사용하여 추가 할 위치를 알지 못한다는 사실을 읽었습니다.

답변

0

VS에서 검색 할 때 나타나는 기본 패키지는 새로 고침 토큰을 가져 오는 것을 지원하지 않는 'Xamarin Auth 1.3.0'입니다.

내 자신의 버전을 OAuth2Authenticator으로 만들었으며 매우 제한된 노력으로 같은 목표에 도달했습니다. 안전하게 저장해야 새로 고침 토큰과 AccountStore에

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(); 

이 호출의 결과를 다음과 같이 LoginPageRenderer이에서

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); 
     } 
    } 

} 

가 호출됩니다.

나는 새로운 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은 설치된 자격 증명 사용을 지원하지 않기 때문에 현재로서는이 문제를 해결할 방법이 없습니다.