2012-07-05 25 views
7

Estoy intentando integrar Google Calendar en mi aplicación y tengo algunos problemas con la autorización de OAuth para pasar un RefreshToken. Recibo un AccessToken sin problema, pero la propiedad RefreshToken es nula. Ver la línea marcada "ERROR AQUÍ:" de donde yo estoy teniendo el problema¿Por qué no recibo un RefreshToken de una solicitud de Google OAuth?

controlador

Mi Asp.Net MVC (llamado OAuthController) tiene el siguiente aspecto:

public ActionResult Index() 
    { 
     var client = CreateClient(); 
     client.RequestUserAuthorization(new[] { "https://www.googleapis.com/auth/calendar" }, new Uri("http://localhost/FL.Evaluation.Web/OAuth/CallBack")); 

     return View(); 
    } 

    public ActionResult CallBack() 
    { 

     if (string.IsNullOrEmpty(Request.QueryString["code"])) return null; 

     var client = CreateClient(); 

     // Now getting a 400 Bad Request here 
     var state = client.ProcessUserAuthorization(); 

     // ERROR HERE: The RefreshToken is NULL 
     HttpContext.Session["REFRESH_TOKEN"] = Convert.ToBase64String(Encoding.Unicode.GetBytes(state.RefreshToken)); 

     return JavaScript("Completed!"); 
    } 

    private static WebServerClient CreateClient() 
    { 
     return 
      new WebServerClient(
       new AuthorizationServerDescription() 
        { 
         TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"), 
         AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"), 
         ProtocolVersion = ProtocolVersion.V20 
        } 
       , _GoogleClientId, _GoogleSecret); 
    } 

veo en los documentos de la API de Google, que Necesito asegurarme de que el access_type solicitado esté configurado en offline para que se envíe un RefreshToken. ¿Cómo configuro este valor en mi solicitud de Autenticador?

+0

He adelgazado mi código, y ahora recibo un error de 400 solicitudes incorrectas al intentar consultar a Google. –

+0

He resuelto mi problema codificando manualmente las HttpRequests de ida y vuelta a Google. Voy a desinfectar y publicar el código como una respuesta –

Respuesta

12

Después de horas de juguetear con DotNetOpenAuth y Google APIs publicado para .Net, no llegué a ninguna parte rápidamente. Decidí eludir las bibliotecas y fui directamente al Google REST API con objetos nativos HttpRequest y HttpResponse. Mi código desinfectado para mi controlador MVC sigue:

private static string _GoogleClientId = "CLIENT_ID"; 
    private static string _GoogleSecret = "SECRET"; 
    private static string _ReturnUrl = "http://localhost/OAuth/CallBack"; 

    public ActionResult Index() 
    { 
     return Redirect(GenerateGoogleOAuthUrl()); 
    } 

    private string GenerateGoogleOAuthUrl() 
    { 

     //NOTE: Key piece here, from Andrew's reply -> access_type=offline forces a refresh token to be issued 
     string Url = "https://accounts.google.com/o/oauth2/auth?scope={0}&redirect_uri={1}&response_type={2}&client_id={3}&state={4}&access_type=offline&approval_prompt=force"; 
     string scope = UrlEncodeForGoogle("https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.readonly").Replace("%20", "+"); 
     string redirect_uri_encode = UrlEncodeForGoogle(_ReturnUrl); 
     string response_type = "code"; 
     string state = ""; 

     return string.Format(Url, scope, redirect_uri_encode, response_type, _GoogleClientId, state); 

    } 

    private static string UrlEncodeForGoogle(string url) 
    { 
     string UnReservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~"; 
     var result = new StringBuilder(); 

     foreach (char symbol in url) 
     { 
      if (UnReservedChars.IndexOf(symbol) != -1) 
      { 
       result.Append(symbol); 
      } 
      else 
      { 
       result.Append('%' + String.Format("{0:X2}", (int)symbol)); 
      } 
     } 

     return result.ToString(); 
    } 

    class GoogleTokenData 
    { 
     public string Access_Token { get; set; } 
     public string Refresh_Token { get; set; } 
     public string Expires_In { get; set; } 
     public string Token_Type { get; set; } 
    } 

    public ActionResult CallBack(string code, bool? remove) 
    { 

     if (remove.HasValue && remove.Value) 
     { 
      Session["GoogleAPIToken"] = null; 
      return HttpNotFound(); 
     } 

     if (string.IsNullOrEmpty(code)) return Content("Missing code"); 

     string Url = "https://accounts.google.com/o/oauth2/token"; 
     string grant_type = "authorization_code"; 
     string redirect_uri_encode = UrlEncodeForGoogle(_ReturnUrl); 
     string data = "code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type={4}"; 

     HttpWebRequest request = HttpWebRequest.Create(Url) as HttpWebRequest; 
     string result = null; 
     request.Method = "POST"; 
     request.KeepAlive = true; 
     request.ContentType = "application/x-www-form-urlencoded"; 
     string param = string.Format(data, code, _GoogleClientId, _GoogleSecret, redirect_uri_encode, grant_type); 
     var bs = Encoding.UTF8.GetBytes(param); 
     using (Stream reqStream = request.GetRequestStream()) 
     { 
      reqStream.Write(bs, 0, bs.Length); 
     } 

     using (WebResponse response = request.GetResponse()) 
     { 
      var sr = new StreamReader(response.GetResponseStream()); 
      result = sr.ReadToEnd(); 
      sr.Close(); 
     } 

     var jsonSerializer = new JavaScriptSerializer(); 
     var tokenData = jsonSerializer.Deserialize<GoogleTokenData>(result); 
     Session["GoogleAPIToken"] = tokenData.Access_Token; 

     return JavaScript("Refresh Token: " + tokenData.Refresh_Token); 

    } 

Muchas gracias a Kelp para un bit del código en este fragmento.

+2

gracias por el código, realmente funciona :) –

9

Ajuste GoogleAuthenticationServer.Description para tener un URI de punto final de autorización que incluya ?access_type=offline en la cadena de consulta.

+0

Gracias por esta sugerencia muy útil 'AuthorizationServerDescription descServer = GoogleAuthenticationServer.Description; descServer.AuthorizationEndpoint = new Uri (descServer.AuthorizationEndpoint.ToString() + "? Access_type = offline"); ' lo usé de esta manera y funcionó para mí – RohitWagh

0

sólo tiene que añadir

AccessType = "fuera de línea",

a GoogleOAuth2AuthenticationOptions (objeto).

Cuestiones relacionadas