2010-09-16 26 views
10

Estoy tratando de usar el CTP para conectarme con Facebook a través de OAuth 2.0.DotNetOpenAuth CTP - solicitud incorrecta de Facebook

puedo obtener la solicitud inicial de Facebook trabaja bien, pero cuando se trata de volver y lo llamamos:

// Where null will become an HttpRequestInfo object 
client.ProcessUserAuthorization(null); 

me sale:

The remote server returned an error: (400) Bad Request.

realidad no he hecho mucho con la base de código inicial; simplemente establece los valores opcionales en nulo (todavía estamos en .NET 3.5). Cualquier pista sería muy apreciada.

Además, y supongo que esto es más una pregunta para Andrew específicamente; ¿Hay un foro/blog para cualquiera de estas cosas, o en cualquier lugar que brinde actualizaciones periódicas? Sería muy bueno saber algunas cosas:

  1. fecha de lanzamiento prevista del DotNetOpenAuth con OAuth 2.0
  2. Si .NET 4.0 será un requisito previo

De todos modos, cualquier sugerencia será bienvenido

Respuesta

21

Después de resolver este problema, escribí mi propio código para autorizar y obtener los detalles de los usuarios. Otro enfoque sería usar Facebook C# SDK. Como iniciador para cualquier otra persona que piense en hacerlo, así es como lo hice. Tenga en cuenta que no he investigado los casos de error.

En primer lugar, read facebooks doc sobre cómo funciona (! Su bastante simple)

que consumen esta manera:

private static readonly FacebookClient facebookClient = new FacebookClient(); 
public ActionResult LoginWithFacebook() 
{ 
    var result = facebookClient.Authorize(); 
    if (result == FacebookAuthorisationResult.RequestingCode) 
    { 
     //The client will have already done a Response.Redirect 
     return View(); 
    } else if (result == FacebookAuthorisationResult.Authorized) 
    { 
     var user = facebookClient.GetCurrentUser(); 
    } 
    return Redirect("/"); 
} 

Y el código de cliente:

using System; 
using System.IO; 
using System.Net; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Json; 
using System.Text; 
using System.Web; 

namespace Web.Services 
{ 
    public enum FacebookAuthorisationResult 
    { 
     Denied, 
     Authorized, 
     RequestingCode 
    } 
    public class FacebookClient 
    { 
     private const String SESSION_NAME_TOKEN = "UserFacebookToken"; 
     public FacebookClient() 
     { 
      TokenEndpoint = new Uri("https://graph.facebook.com/oauth/access_token"); 
      AuthorizationEndpoint = new Uri("https://graph.facebook.com/oauth/authorize"); 
      MeGraphEndpoint = new Uri("https://graph.facebook.com/me"); 
      ClientIdentifier = "xxxxxxxxxxxxxxxxxx"; 
      Secret = "xxxxxxxxxxxx"; 
      LocalSubDomain = "local.xxxxxxx.com"; 
     } 

     public Uri TokenEndpoint { get; set; } 
     public Uri AuthorizationEndpoint { get; set; } 
     public Uri MeGraphEndpoint { get; set; } 
     public String Secret { get; set; } 
     public String ClientIdentifier { get; set; } 
     private String LocalSubDomain { get; set; } 


     public FacebookAuthorisationResult Authorize() 
     { 
      var errorReason = HttpContext.Current.Request.Params["error_reason"]; 
      var userDenied = errorReason != null; 
      if (userDenied) 
       return FacebookAuthorisationResult.Denied; 
      var verificationCode = HttpContext.Current.Request.Params["code"]; 
      var redirectUrl = GetResponseUrl(HttpContext.Current.Request.Url); 
      var needToGetVerificationCode = verificationCode == null; 
      if (needToGetVerificationCode) 
      { 
       var url = AuthorizationEndpoint + "?" + 
          "client_id=" + ClientIdentifier + "&" + 
          "redirect_uri=" + redirectUrl; 
       HttpContext.Current.Response.Redirect(url); 
       return FacebookAuthorisationResult.RequestingCode; 
      } 
      var token = ExchangeCodeForToken(verificationCode, redirectUrl); 
      HttpContext.Current.Session[SESSION_NAME_TOKEN] = token; 
      return FacebookAuthorisationResult.Authorized; 
     } 
     public Boolean IsCurrentUserAuthorized() 
     { 
      return HttpContext.Current.Session[SESSION_NAME_TOKEN] != null; 
     } 
     public FacebookGraph GetCurrentUser() 
     { 
      var token = HttpContext.Current.Session[SESSION_NAME_TOKEN]; 
      if (token == null) 
       return null; 
      var url = MeGraphEndpoint + "?" + 
         "access_token=" + token; 
      var request = WebRequest.CreateDefault(new Uri(url)); 
      using (var response = request.GetResponse()) 
      { 
       using (var responseStream = response.GetResponseStream()) 
       { 
        using (var responseReader = new StreamReader(responseStream)) 
        { 
         var responseText = responseReader.ReadToEnd(); 
         var user = FacebookGraph.Deserialize(responseText); 
         return user; 
        } 
       } 
      } 
     } 
     private String ExchangeCodeForToken(String code, Uri redirectUrl) 
     { 
      var url = TokenEndpoint + "?" + 
         "client_id=" + ClientIdentifier + "&" + 
         "redirect_uri=" + redirectUrl + "&" + 
         "client_secret=" + Secret + "&" + 
         "code=" + code; 
      var request = WebRequest.CreateDefault(new Uri(url)); 
      using (var response = request.GetResponse()) 
      { 
       using (var responseStream = response.GetResponseStream()) 
       { 
        using (var responseReader = new StreamReader(responseStream)) 
        { 
         var responseText = responseReader.ReadToEnd(); 
         var token = responseText.Replace("access_token=", ""); 
         return token; 
        } 
       } 
      } 
     } 
     private Uri GetResponseUrl(Uri url) 
     { 
      var urlAsString = url.ToString(); 
      var doesUrlContainQuestionMark = urlAsString.Contains("?"); 
      if (doesUrlContainQuestionMark) 
      { 
       // Remove any parameters. Apparently Facebook does not support state: http://forum.developers.facebook.net/viewtopic.php?pid=255231 
       // If you do not do this, you will get 'Error validating verification code' 
       urlAsString = urlAsString.Substring(0, urlAsString.IndexOf("?")); 
      } 
      var replaceLocalhostWithSubdomain = url.Host == "localhost"; 
      if (!replaceLocalhostWithSubdomain) 
       return new Uri(urlAsString); 
      // Facebook does not like localhost, you can only use the configured url. To get around this, log into facebook 
      // and set your Site Domain setting, ie happycow.com. 
      // Next edit C:\Windows\System32\drivers\etc\hosts, adding the line: 
      // 127.0.0.1  local.happycow.cow 
      // And lastly, set LocalSubDomain to local.happycow.cow 
      urlAsString = urlAsString.Replace("localhost", LocalSubDomain); 
      return new Uri(urlAsString); 
     } 
    } 
    [DataContract] 
    public class FacebookGraph 
    { 
     private static DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(FacebookGraph)); 
      // Note: Changed from int32 to string based on Antonin Jelinek advise of an overflow 
     [DataMember(Name = "id")] 
     public string Id { get; set; } 

     [DataMember(Name = "name")] 
     public string Name { get; set; } 

     [DataMember(Name = "first_name")] 
     public string FirstName { get; set; } 

     [DataMember(Name = "last_name")] 
     public string LastName { get; set; } 

     [DataMember(Name = "link")] 
     public Uri Link { get; set; } 

     [DataMember(Name = "birthday")] 
     public string Birthday { get; set; } 

     public static FacebookGraph Deserialize(string json) 
     { 
      if (String.IsNullOrEmpty(json)) 
      { 
       throw new ArgumentNullException("json"); 
      } 

      return Deserialize(new MemoryStream(Encoding.UTF8.GetBytes(json))); 
     } 

     public static FacebookGraph Deserialize(Stream jsonStream) 
     { 
      if (jsonStream == null) 
      { 
       throw new ArgumentNullException("jsonStream"); 
      } 

      return (FacebookGraph)jsonSerializer.ReadObject(jsonStream); 
     } 
    } 


} 
+0

¿Dónde puedo obtener 'ClientIdentifier =" xxxxxxxxxxxxxxxxxxxxxxxx "; Secret =" xxxxxxxxxxxx "; LocalSubDomain =" local.xxxxxxx.com ";' values ​​from? – capdragon

+3

@capdragon: Google "facebook api key" y prueba una de las guías, como [this one] (http://help.webscribble.com/display/jconnector/Getting+the+Facebook+Connect+API+Key) . – Iain

+0

¡El código bueno funciona para mí! Puede necesitar solicitar permiso de correo electrónico explícitamente para su aplicación de Facebook, en ese caso debe agregar "& scope = email" a su llamada de punto final de autorización, así: if (needToGetVerificationCode) { var url = AuthorizationEndpoint + "? " + "client_id =" + ClientIdentifier + "&" + "redirect_uri =" + redirectUrl + "&" + "scope = email"; HttpContext.Current.Response.Redirect (url); return FacebookAuthorisationResult.RequestingCode; } – HOKBONG

0

Descubrí que escribir mi propia implementación consumía menos tiempo que jugar con DNOA. No es muy difícil, aunque realmente no he hecho una revisión de seguridad exhaustiva del código; que supongo que sería una advertencia importante.

Probablemente no sea tan útil, pero descubrí que solo tomaba 1/2 día para que funcionara algo.

1

solución de Iain es finalmente algo con lo que hice que esto funcionara.

Hay una nota para futuros implementadores: parece que la propiedad de ID de Facebook ahora excede la capacidad del tipo Int32. Es posible que deba cambiar esto en la clase FacebookGraph, utilicé una cadena simple.

Gracias Iain, ¡su código realmente me ayudó!

+0

Gracias por el consejo, he actualizado la respuesta según su sugerencia – Iain

0

Después de perder el tiempo con una actualización de DotNetOpenAuth por un largo tiempo y sin experimentar ninguna suerte al conectarme a Facebook, también armé un código para admitir el inicio de sesión de Facebook desde mi aplicación ASP.NET MVC.

En primer lugar, un código como este debe ir en un controlador en alguna parte.

// You call this action to initiate the process with Facebook 
public ActionResult FacebookLogIn() 
{ 
    return CreateFacebookClient().RequestAuthorisation(); 
} 

// Facebook will call you back here 
public ActionResult FacebookAuthorisationResponse() 
{ 
    var facebookClient = CreateFacebookClient(); 
    var authorisationResponse = facebookClient.HandleAuthorisationResponse(); 

    if (authorisationResponse.IsSuccess) 
    { 
     var accessToken = authorisationResponse.AccessToken; 

     // TODO do whatever you want to do with your access token here 

     return Redirect("SomeUrl"); 
    } 

    // TODO handle the error somehow 
    return Content(authorisationResponse.ErrorMessage); 
} 

private FacebookClient CreateFacebookClient() 
{ 
    const string clientId = "xxxxxxxxxxxxxxx"; 
    const string appSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 

    var redirectUrl = Url.Action("FacebookAuthorisationResponse", null, null, "http"); 

    return new FacebookClient(clientId, appSecret, redirectUrl); 
} 

Eso es prácticamente todo lo que tiene que hacer con su código. Una vez que tiene ese token de acceso, puede hacer cosas como esta:

// Get basic information for this user 
var basicInfoUrl = string.Format("https://graph.facebook.com/me?access_token={0}", Uri.EscapeDataString(accessToken.TokenString)); 
var json = new WebClient().DownloadString(basicInfoUrl); 

Aquí está el código que admite las cosas relativamente simples de arriba. Usted sólo puede volcar todo esto en un archivo en el proyecto:

// Drew Noakes, http://drewnoakes.com 
// Created 08/08/2012 22:41 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Net; 
using System.Web; 
using System.Web.Mvc; 

namespace DrewNoakes.Facebook.Mvc 
{ 
    public sealed class FacebookClient 
    { 
     private readonly string _clientId; 
     private readonly string _appSecret; 
     private readonly string _authorisationResponseUrl; 

     public IFacebookClientStateManager StateManager { get; set; } 

     public FacebookClient(string clientId, string appSecret, string authorisationResponseUrl) 
     { 
      _clientId = clientId; 
      _appSecret = appSecret; 
      _authorisationResponseUrl = authorisationResponseUrl; 

      StateManager = MemoryStateManager.Instance; 
     } 

     public ActionResult RequestAuthorisation(string[] permissions = null) 
     { 
      // First step is to redirect the visitor's browser to Facebook 

      var state = StateManager.GetState(); 

      var url = string.Format("https://www.facebook.com/dialog/oauth?client_id={0}&redirect_uri={1}&scope={2}&state={3}", 
       _clientId, Uri.EscapeDataString(_authorisationResponseUrl), permissions == null ? string.Empty : string.Join(",", permissions), state); 

      return new RedirectResult(url, permanent: false); 
     } 

     public AuthorisationResponse HandleAuthorisationResponse() 
     { 
      var queryString = HttpContext.Current.Request.QueryString; 

      // Ensure returned state is expected 
      if (!StateManager.IsValidState(queryString["state"])) 
       return AuthorisationResponse.Error("Invalid state"); 

      // TODO handle case where user declined: YOUR_REDIRECT_URI?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request.&state=YOUR_STATE_VALUE 

      var code = queryString["code"]; 
      var url = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&code={3}&client_secret={2}", 
       _clientId, Uri.EscapeDataString(_authorisationResponseUrl), _appSecret, Uri.EscapeDataString(code)); 

      var client = new WebClient { Proxy = null }; 
      var responseBody = client.DownloadString(url); 

      // HTTP 200: access_token=USER_ACCESS_TOKEN&expires=NUMBER_OF_SECONDS_UNTIL_TOKEN_EXPIRES 
      // HTTP 400: TODO handle JSON error reponse: { "error": { "type": "OAuthException", "message": "Error validating verification code." } } 

      var response = HttpUtility.ParseQueryString(responseBody); 
      var accessToken = response["access_token"]; 
      var expiresSecondsString = response["expires"]; 

      int expiresSeconds; 
      if (!int.TryParse(expiresSecondsString, out expiresSeconds)) 
       return AuthorisationResponse.Error("Unable to parse expiration time"); 
      var expiresAtUtc = DateTime.UtcNow.AddSeconds(expiresSeconds); 

      return AuthorisationResponse.Success(accessToken, expiresAtUtc); 
     } 
    } 

    public class AuthorisationResponse 
    { 
     public bool IsSuccess { get; private set; } 
     public AccessToken AccessToken { get; private set; } 
     public string ErrorMessage { get; private set; } 

     private AuthorisationResponse() { } 

     public static AuthorisationResponse Error(string errorMessage) 
     { 
      return new AuthorisationResponse { IsSuccess = false, ErrorMessage = errorMessage }; 
     } 

     public static AuthorisationResponse Success(string accessToken, DateTime expiresAtUtc) 
     { 
      return new AuthorisationResponse { IsSuccess = true, AccessToken = new AccessToken(accessToken, expiresAtUtc) }; 
     } 
    } 

    public struct AccessToken 
    { 
     public string TokenString { get; private set; } 
     public DateTime ExpiresAtUtc { get; private set; } 

     public AccessToken(string tokenString, DateTime expiresAtUtc) 
      : this() 
     { 
      if (tokenString == null) 
       throw new ArgumentNullException("tokenString"); 
      TokenString = tokenString; 
      ExpiresAtUtc = expiresAtUtc; 
     } 
    } 

    public interface IFacebookClientStateManager 
    { 
     string GetState(); 

     bool IsValidState(string state); 
    } 

    /// <summary> 
    /// The default implementation of <see cref="IFacebookClientStateManager"/>. 
    /// </summary> 
    public sealed class MemoryStateManager : IFacebookClientStateManager 
    { 
     private static readonly IFacebookClientStateManager _instance = new MemoryStateManager(); 

     public static IFacebookClientStateManager Instance 
     { 
      get { return _instance; } 
     } 

     private readonly Dictionary<string, DateTime> _stateTimes = new Dictionary<string, DateTime>(); 

     public string GetState() 
     { 
      var state = Guid.NewGuid().ToString("N"); 
      _stateTimes[state] = DateTime.UtcNow; 
      return state; 
     } 

     public bool IsValidState(string state) 
     { 
      var isValid = _stateTimes.Remove(state); 

      // Remove any keys that have not been accessed within a given period 
      var staleKeys = _stateTimes.Where(pair => pair.Value < DateTime.UtcNow.AddMinutes(-30)).Select(pair => pair.Key).ToList(); 

      foreach (var staleKey in staleKeys) 
       _stateTimes.Remove(staleKey); 

      return isValid; 
     } 
    } 
} 

me tiró juntos rápidamente esta noche, pero va a volver más tarde y el parche si encuentro problemas. Sin embargo, está funcionando realmente bien en mi sitio ahora.

Hay un par de TODO relacionados con el manejo robusto de la respuesta de error.

0

He estado, de forma intermitente, experimentando el mismo problema cuando estaba utilizando el parámetro returnTo de WebServerClient 's PrepareRequestUserAuthorization(). Solo ciertos URI returnTo experimentarían un problema ... los URI que estaba pasando tenían un componente Base64 para ellos. Algunos de los cuales contenían a = en ellos. Si el URL codifica estas URL obtengo "Se detectó un valor Request.Path potencialmente peligroso a partir del error del cliente (%)" de mi servidor local.

Hasta que encuentre una mejor solución, estoy realizando un pinchado en la cuerda antes de pasarla;

localReturnTo = localReturnTo.Replace("=", "_") 

Luego cuando recibo mi respuesta, realizo el reverso;

returnedUri = returnedUri.Replace("_", "=") 

No es bonito. Pero soluciona el problema inmediato (similar) que estaba experimentando.

Cuestiones relacionadas