10 votes

DotNetOpenAuth CTP - Facebook bad request

J'essaie d'utiliser le CTP pour me connecter à Facebook via OAuth 2.0.

J'arrive à faire fonctionner la demande initiale à Facebook, mais lorsqu'elle revient et que nous l'appelons :

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

Je reçois :

Le serveur distant a renvoyé une erreur : (400) Bad Request.

Je n'ai pas fait grand-chose avec la base de code initiale ; j'ai simplement mis les valeurs optionnelles à null (nous sommes toujours sur .NET 3.5). Tout indice serait très apprécié.

Par ailleurs, et je pense que cette question s'adresse plus particulièrement à Andrew, existe-t-il un forum ou un blog sur ce sujet, ou un endroit où l'on peut trouver des mises à jour régulières ? Ce serait bien de savoir certaines choses :

  1. Date de sortie prévue de DotNetOpenAuth avec OAuth 2.0
  2. Si .NET 4.0 sera un pré-requis

Quoi qu'il en soit, toute suggestion sera la bienvenue.

22voto

Iain Points 3903

Après avoir rencontré ce problème, j'ai écrit mon propre code pour autoriser et obtenir les détails de l'utilisateur. Une autre approche consisterait à utiliser SDK C# de Facebook . Voici comment j'ai procédé pour donner un coup de pouce à tous ceux qui envisagent de le faire eux-mêmes. Veuillez noter que je n'ai pas examiné les cas d'erreur.

Premièrement, lire le document de facebooks sur son fonctionnement (c'est assez simple !)

Je le consomme comme suit :

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("/");
}

Et le code client :

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

}

1voto

Antonin Jelinek Points 347

La solution de Iain est enfin quelque chose que j'ai réussi à faire fonctionner.

Il y a une remarque pour les futurs implémenteurs - il semble que la propriété Facebook ID dépasse maintenant la capacité du type Int32. Vous devrez peut-être modifier cela dans la classe FacebookGraph, j'ai utilisé une simple chaîne de caractères.

Merci Iain, votre code m'a vraiment aidé !

0voto

peteisace Points 1063

J'ai constaté qu'écrire ma propre implémentation prenait moins de temps que de jouer avec DNOA. Ce n'est pas très difficile, bien que je n'aie pas vraiment effectué un contrôle de sécurité approfondi du code, ce qui, je suppose, serait une mise en garde majeure.

Ce n'est probablement pas très utile, mais j'ai constaté qu'il ne fallait qu'une demi-journée pour faire fonctionner quelque chose.

0voto

Drew Noakes Points 69288

Après avoir essayé une mise à jour de DotNetOpenAuth pendant un long moment et n'avoir pas réussi à me connecter à Facebook, j'ai également mis au point du code pour supporter la connexion à Facebook à partir de mon application ASP.NET MVC.

Tout d'abord, ce type de code doit être placé quelque part dans un contrôleur.

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

C'est à peu près tout ce que vous devez faire avec votre code. Une fois que vous avez ce jeton d'accès, vous pouvez faire des choses comme ceci :

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

Le code qui prend en charge les éléments relativement simples ci-dessus se trouve ici. Vous pouvez simplement mettre tout cela dans un fichier de votre projet :

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

J'ai fait ça rapidement ce soir, mais je reviendrai plus tard pour le corriger si je trouve des problèmes. Cela fonctionne très bien sur mon site pour l'instant.

Il y a quelques TODOs liés à la gestion robuste des réponses d'erreur.

0voto

MrMDavidson Points 1591

J'ai moi-même rencontré, par intermittence, ce même problème lorsque j'utilisais l'application returnTo paramètre de WebServerClient 's PrepareRequestUserAuthorization() . Seules certaines returnTo Les URI posaient un problème... les URI que je transmettais avaient un composant Base64. Certains d'entre eux contenaient un =. Si j'encode ces URLs, j'obtiens une erreur "A potentially dangerous Request.Path value was detected from the client (%)" de mon serveur local.

Jusqu'à ce que je trouve une meilleure solution, j'effectue quelques manipulations sur la chaîne avant de la transmettre ;

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

Ensuite, lorsque je reçois ma réponse, je fais l'inverse ;

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

Ce n'est pas beau à voir. Mais cela permet de contourner le problème immédiat (similaire) que je rencontrais.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X