.NET ASP.NET MVC приложение Facebook OAuth с дефиниран обхват

Кой е най-лесният начин за създаване на .NET ASP.NET MVC приложение с Facebook OAuth с дефиниран обхват?

Опитах много примери. OAuthWebSecurity.RegisterClient не поддържа добавяне на повече обхват на Facebook. шаблон за приложение във Facebook създава платно и Имам нужда от приложение без платно. FacebookScopedClient не е пълен и не успя да работи с тази корекция.

Какво предлагаш?

Също така отварям за решения на JavaScript/jQuery.


person Liad Livnat    schedule 20.02.2013    source източник


Отговори (2)


Вероятно съм закъснял, но попаднах на този проблем преди няколко дни и намерих само няколко лоши решения в мрежата. Така че написах собственото си решение и мога да го публикувам едва сега.

Възползвах се от класа DotNetOpenAuth.AspNet.Clients.OAuth2Client, който върши по-голямата част от работата. Разширих го само, за да включа обхват и допълнителни потребителски данни.

public class FacebookExtendedClient : DotNetOpenAuth.AspNet.Clients.OAuth2Client
{   
    protected FacebookClient facebookClient;
    protected string fields;
    protected string scope;
    protected Func<string, object, string> fieldTransformer;
    protected bool emailAsUsername;
    protected IDictionary<string, string> userData;

    private string[] splittedFields;
    private string[] splittedScope;

    protected const string serviceLoginBaseUrl = "https://www.facebook.com/dialog/oauth";
    protected const string serviceMeBaseUrl = "https://graph.facebook.com/me";
    protected const string serviceAccessTokenBaseUrl = "https://graph.facebook.com/oauth/access_token";

    /// <summary>
    /// Create an instrance of the class.
    /// </summary>
    /// <param name="appId">The App ID of the application used to connect to Facebook service.</param>
    /// <param name="appSecret">The App Secret of the application used to connect to Facebook service.</param>
    /// <param name="fields">
    /// String containing comma separated fields to add to the request.
    /// If empty the request will retrieve the default fields based of the specified scope.
    /// </param>
    /// <param name="fieldTransformer">
    /// Function to be applied to the values retrived from facebook.
    /// If null provided the method will try to cast values from object to string explicitly,
    /// an InvalidCastException will be thrown if the cast will not be possible.
    /// </param>
    /// <param name="scope">
    /// String containing comma separated permissions to add to the request.
    /// If empty the request will have the basic scope.
    /// </param>
    /// <param name="emailAsUsername">Makes the email of the facebook user used as authentication username.</param>
    public FacebookExtendedClient(string appId, string appSecret, string fields = "", Func<string, object, string> fieldTransformer = null, string scope = "", bool emailAsUsername = false)
        : base("facebook")
    {
        if (string.IsNullOrEmpty(appId))
            throw new ArgumentException("The appId argument can not be null or empty.", "appId");
        if (string.IsNullOrEmpty(appSecret))
            throw new ArgumentException("The appSecret argument can not be null or empty.", "appSecret");

        fields = fields.Replace(" ", "");
        scope = scope.Replace(" ", "");
        this.splittedFields = fields.Split(',');
        this.splittedScope = scope.Split(',');

        if (emailAsUsername == true && !this.splittedFields.Contains("email") && !this.splittedScope.Contains("email"))
            throw new ArgumentException("The scope argument must contain the 'email' permission and the 'email' field to allow emailAsUsername to true.", "scope");

        this.facebookClient = new FacebookClient();
        this.facebookClient.AppId = appId;
        this.facebookClient.AppSecret = appSecret;
        this.fields = fields;
        this.fieldTransformer = fieldTransformer;
        this.scope = scope;
        this.emailAsUsername = emailAsUsername;
    }

    public FacebookClient FacebookClient
    {
        get
        {
            return this.facebookClient;
        }
    }

    public IDictionary<string, string> UserData
    {
        get
        {
            return this.userData;
        }
    }

    protected override Uri GetServiceLoginUrl(Uri returnUrl)
    {
        Dictionary<string, object> parameters = new Dictionary<string, object>();
        parameters.Add("redirect_uri", returnUrl.AbsoluteUri);

        if (!string.IsNullOrEmpty(this.scope))
            parameters.Add("scope", this.scope);

        return this.facebookClient.GetLoginUrl(parameters);
    }

    protected override IDictionary<string, string> GetUserData(string accessToken)
    {
        // This method makes the AuthenticationResult's UserName property be the facebook username of the logged user,
        // but if the facebook username is missing the facebook id will be used.
        // If emailAsUsername is true then AuthenticationResult's UserName property is the email retrieved from facebook
        // and the facebook username can be retrieved by the key "fb_username" in this.userData

        FacebookClient facebookClient = new FacebookClient(accessToken);

        var getResult = facebookClient.Get<IDictionary<string, object>>("me", new { fields = this.fields });
        Dictionary<string, string> result = new Dictionary<string, string>();

        if (this.fieldTransformer != null)
        {
            foreach (var pair in getResult)
                result.Add(pair.Key, this.fieldTransformer(pair.Key, pair.Value));
        }
        else
        {
            foreach (var pair in getResult)
            {
                string value = pair.Value.ToString();

                if (value == null)
                    throw new InvalidCastException("Cast not possible for the object associate to the key '" + pair.Key + "'.");

                result.Add(pair.Key, value);
            }
        }

        if (this.splittedFields.Contains("username"))
            result["fb_username"] = result["username"];

        if (this.emailAsUsername)
            result["username"] = result["email"];

        this.userData = result;

        return result;
    }

    protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
    {
        UriBuilder builder = new UriBuilder(serviceAccessTokenBaseUrl);
        builder.Query = string.Format("client_id={0}&client_secret={1}&redirect_uri={2}&code={3}",
            this.facebookClient.AppId, this.facebookClient.AppSecret, HttpUtility.UrlEncode(Encoding.ASCII.GetBytes(returnUrl.AbsoluteUri)), authorizationCode);

        using (WebClient client = new WebClient())
        {
            string str = client.DownloadString(builder.Uri);

            if (string.IsNullOrEmpty(str))
                return null;

            return HttpUtility.ParseQueryString(str)["access_token"];
        }
    }
}

Можете да го използвате и като го регистрирате в OAuthWebSecurity по този начин (поставете метода RegisterAuth в Application_Start, както е в шаблона InternetApplication):

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        configuration.LoadFromAppSettings();

        OAuthWebSecurity.RegisterClient(new FacebookExtendedClient(
            "##YOUR_APP_ID##",
            "##YOUR_APP_SECRET##",
            "id,first_name,last_name,link,username,gender,email,age_range,picture.height(200)",
            new Func<string, object, string>(fieldsTransformer),
            "email"));
    }

    private static string fieldsTransformer(string key, object value)
    {
        switch (key)
        {
            case "picture":
                var data = (value as IDictionary<string, object>)["data"] as IDictionary<string, object>;
                return data["url"].ToString();
            case "age_range":
                var min = (value as IDictionary<string, object>)["min"];
                return min.ToString();
            default:
                return value.ToString();
        }
    }
}

Както можете да видите в примера по-горе, методът fieldsTransformer ще получи ключа и стойността за всяко избрано поле, в този случай той ще трансформира обекта, извлечен от facebook за картината, в url на картината. Това е удобен метод, ако е осигурено null за параметъра Func, JSON представянето на обекта стойности ще бъде запазено.

Извличането на информацията за клиента по-късно, след влизане, може да стане по следния начин:

[Authorize]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        IDictionary<string, string> userData = (OAuthWebSecurity.GetOAuthClientData("facebook").AuthenticationClient as FacebookExtendedClient).UserData;
        string email = userData["email"];

        // If leave null the fieldTransform of the client you can access to complex properties like this:
        JObject picture = JObject.Parse(userData["picture"]);
        string url = (picture["data"] as JObject)["url"].ToString();

        ViewBag.Email = userData["email"];
        ViewBag.PictureUrl = url;
        return View();
    }
}

Надявам се да харесате този код, дори и да е малко късно! :)

person Paolo Dragone    schedule 26.04.2013
comment
Благодаря, че сподели този Дунадан. Какво е пространството от имена за FacebookClient в класа FacebookExtendedClient? Ако използвам предложеното пространство от имена DotNetOpenAuth.AspNet.Clients, получавам много синтактични грешки за използване на FacebookClient. - person Amethi; 07.05.2013
comment
А, това е клиентът на Facebook, достъпен чрез NuGet. - person Amethi; 07.05.2013
comment
Да, имайте предвид да използвате правилното сглобяване: FacebookClient е този във Facebook пакета, а не DotNetOpenAuth.AspNet.Clients, те за съжаление имат същото име. - person Paolo Dragone; 07.05.2013
comment
Харесвам този подход и използвах нещо много подобно, преди да видя тази публикация. Притеснението ми е, че ако вашето приложение изисква различни разрешения по различно време по време на различни потребителски сценарии, как да го параметризирате, без да получавате множество подкласове на OAuth2Client, всеки инициализиран с различен обхват? - person John Grant; 17.07.2013
comment
@Dunadan, в проект MVC 4 го внедрих така, първо за да покажа Facebook в името на бутона (@p.Displayname), в _ExternalLoginsListPartial.cshtml е необходима следната корекция във файла AuthConfig.cs: OAuthWebSecurity.RegisterClient (нов FacebookExtendedClient(...имейл), Facebook, нула); 2-ро: кодът, който сте добавили в HomeController/Index (като тестване предполагам), трябва да бъде поставен/персонализиран в Accountcontroller/ExternalLoginCallback. Това каза много благодаря за вашия код, много ми хареса тази реализация! - person firepol; 02.07.2014

Пиша първото си приложение за Facebook с уеб приложение C# MVC 5 във VS 2013.

Във файла AppStart\StartupAuth.cs направих следните модификации.

Уверете се, че следните изрази за използване са в класа:

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Facebook;
using Owin;`

След това в секцията за удостоверяване на Facebook в долната част на метода ConfigureAuth:

FacebookAuthenticationOptions fbOptions = new FacebookAuthenticationOptions();
fbOptions.AppId="YOUR_APP_ID";
fbOptions.AppSecret="YOUR_APP_SECRET";
fbOptions.Scope.Add("user_about_me");
fbOptions.Scope.Add("user_actions.books");
fbOptions.Scope.Add("user_actions.music");
//...more at the bottom
fbOptions.Scope.Add("video_upload");
fbOptions.Scope.Add("xmpp_login");
app.UseFacebookAuthentication(fbOptions);

Това е! По-долу е пълният списък към 2/1/2014:

fbOptions.Scope.Add("user_about_me");
fbOptions.Scope.Add("user_actions.books");
fbOptions.Scope.Add("user_actions.music");
fbOptions.Scope.Add("user_actions.news");
fbOptions.Scope.Add("user_actions.video");
fbOptions.Scope.Add("user_activities");
fbOptions.Scope.Add("user_birthday");
fbOptions.Scope.Add("user_checkins");
fbOptions.Scope.Add("user_education_history");
fbOptions.Scope.Add("user_events");
fbOptions.Scope.Add("user_friends");
fbOptions.Scope.Add("user_games_activity");
fbOptions.Scope.Add("user_groups");
fbOptions.Scope.Add("user_hometown");
fbOptions.Scope.Add("user_interests");
fbOptions.Scope.Add("user_likes");
fbOptions.Scope.Add("user_location");
fbOptions.Scope.Add("user_notes");
fbOptions.Scope.Add("user_online_presence");
fbOptions.Scope.Add("user_photo_video_tags");
fbOptions.Scope.Add("user_photos");
fbOptions.Scope.Add("user_questions");
fbOptions.Scope.Add("user_relationship_details");
fbOptions.Scope.Add("user_relationships");
fbOptions.Scope.Add("user_religion_politics");
fbOptions.Scope.Add("user_status");
fbOptions.Scope.Add("user_subscriptions");
fbOptions.Scope.Add("user_videos");
fbOptions.Scope.Add("user_website");
fbOptions.Scope.Add("user_work_history");

fbOptions.Scope.Add("friends_about_me");
fbOptions.Scope.Add("friends_actions.books");
fbOptions.Scope.Add("friends_actions.music");
fbOptions.Scope.Add("friends_actions.news");
fbOptions.Scope.Add("friends_actions.video");
fbOptions.Scope.Add("friends_activities");
fbOptions.Scope.Add("friends_birthday");
fbOptions.Scope.Add("friends_checkins");
fbOptions.Scope.Add("friends_education_history");
fbOptions.Scope.Add("friends_events");
fbOptions.Scope.Add("friends_games_activity");
fbOptions.Scope.Add("friends_groups");
fbOptions.Scope.Add("friends_hometown");
fbOptions.Scope.Add("friends_interests");
fbOptions.Scope.Add("friends_likes");
fbOptions.Scope.Add("friends_location");
fbOptions.Scope.Add("friends_notes");
fbOptions.Scope.Add("friends_online_presence");
fbOptions.Scope.Add("friends_photo_video_tags");
fbOptions.Scope.Add("friends_photos");
fbOptions.Scope.Add("friends_questions");
fbOptions.Scope.Add("friends_relationship_details");
fbOptions.Scope.Add("friends_relationships");
fbOptions.Scope.Add("friends_religion_politics");
fbOptions.Scope.Add("friends_status");
fbOptions.Scope.Add("friends_subscriptions");
fbOptions.Scope.Add("friends_videos");
fbOptions.Scope.Add("friends_website");
fbOptions.Scope.Add("friends_work_history");

fbOptions.Scope.Add("ads_management");
fbOptions.Scope.Add("ads_read");
fbOptions.Scope.Add("create_event");
fbOptions.Scope.Add("create_note");
fbOptions.Scope.Add("email");
fbOptions.Scope.Add("export_stream");
fbOptions.Scope.Add("manage_friendlists");
fbOptions.Scope.Add("manage_notifications");
fbOptions.Scope.Add("manage_pages");
fbOptions.Scope.Add("photo_upload");
fbOptions.Scope.Add("publish_actions");
fbOptions.Scope.Add("publish_checkins");
fbOptions.Scope.Add("publish_stream");
fbOptions.Scope.Add("read_friendlists");
fbOptions.Scope.Add("read_insights");
fbOptions.Scope.Add("read_mailbox");
fbOptions.Scope.Add("read_page_mailboxes");
fbOptions.Scope.Add("read_requests");
fbOptions.Scope.Add("read_stream");
fbOptions.Scope.Add("rsvp_event");
fbOptions.Scope.Add("share_item");
fbOptions.Scope.Add("sms");
fbOptions.Scope.Add("status_update");
fbOptions.Scope.Add("video_upload");
fbOptions.Scope.Add("xmpp_login");
person Rich    schedule 02.02.2014