Code Monkey home page Code Monkey logo

Comments (7)

adoconnection avatar adoconnection commented on July 21, 2024 9
public class FacebookClient2017 : OAuth2Client
{
        /// <summary>
        /// The authorization endpoint.
        /// </summary>
        private const string AuthorizationEndpoint = "https://www.facebook.com/v2.8/dialog/oauth";
        /// <summary>
        /// The token endpoint.
        /// </summary>
        private const string TokenEndpoint = "https://graph.facebook.com/v2.8/oauth/access_token";
        /// <summary>
        /// The user info endpoint.
        /// </summary>
        private const string UserInfoEndpoint = "https://graph.facebook.com/v2.8/me";
        /// <summary>
        /// The app id.
        /// </summary>
        private readonly string _appId;
        /// <summary>
        /// The app secret.
        /// </summary>
        private readonly string _appSecret;

        /// <summary>
        /// The requested scopes.
        /// </summary>
        private readonly string[] _requestedScopes;


        /// <summary>
        /// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
        /// </summary>
        /// <param name="appId">The Facebook App Id</param>
        /// <param name="appSecret">The Facebook App Secret</param>
        public FacebookClient2017(string appId, string appSecret) : this(appId, appSecret, new[] { "email" }) { }

        /// <summary>
        /// Creates a new Facebook OAuth2 client.
        /// </summary>
        /// <param name="appId">The Facebook App Id</param>
        /// <param name="appSecret">The Facebook App Secret</param>
        /// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
        public FacebookClient2017(string appId, string appSecret, params string[] requestedScopes) : base("facebook")
        {
            if (string.IsNullOrWhiteSpace(appId))
                throw new ArgumentNullException("appId");

            if (string.IsNullOrWhiteSpace(appSecret))
                throw new ArgumentNullException("appSecret");

            if (requestedScopes == null)
                throw new ArgumentNullException("requestedScopes");

            if (requestedScopes.Length == 0)
                throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");

            _appId = appId;
            _appSecret = appSecret;
            _requestedScopes = requestedScopes;
        }

        public override void RequestAuthentication(HttpContextBase context, Uri returnUrl)
        {
            string redirectUrl = this.GetServiceLoginUrl(returnUrl).AbsoluteUri;
            context.Response.Redirect(redirectUrl, endResponse: true);
        }

        public new AuthenticationResult VerifyAuthentication(HttpContextBase context)
        {
            throw new NoNullAllowedException();
        }

        public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
        {
            string code = context.Request.QueryString["code"];
            if (string.IsNullOrEmpty(code))
            {
                return AuthenticationResult.Failed;
            }

            string accessToken = this.QueryAccessToken(returnPageUrl, code);
            if (accessToken == null)
            {
                return AuthenticationResult.Failed;
            }

            IDictionary<string, string> userData = this.GetUserData(accessToken);
            if (userData == null)
            {
                return AuthenticationResult.Failed;
            }

            string id = userData["id"];
            string name;

            // Some oAuth providers do not return value for the 'username' attribute. 
            // In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id'
            if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name))
            {
                name = id;
            }

            // add the access token to the user data dictionary just in case page developers want to use it
            userData["accesstoken"] = accessToken;

            return new AuthenticationResult(
                isSuccessful: true, provider: this.ProviderName, providerUserId: id, userName: name, extraData: userData);
        }

        protected override Uri GetServiceLoginUrl(Uri returnUrl)
        {
            var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);

            return BuildUri(AuthorizationEndpoint, new NameValueCollection
                {
                    { "client_id", _appId },
                    { "scope", string.Join(" ", _requestedScopes) },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                    { "state", state },
                });
        }

        protected override IDictionary<string, string> GetUserData(string accessToken)
        {
            var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });

            var webRequest = (HttpWebRequest)WebRequest.Create(uri);

            using (var webResponse = webRequest.GetResponse())
            using (var stream = webResponse.GetResponseStream())
            {
                if (stream == null)
                    return null;

                using (var textReader = new StreamReader(stream))
                {
                    var json = textReader.ReadToEnd();
                    var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
                    var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());

                    data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));

                    return data;
                }
            }
        }

        public string QueryAccessTokenByCode(Uri returnUrl, string authorizationCode)
        {
            return this.QueryAccessToken(returnUrl, authorizationCode);
        }

        protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
        {
            var uri = BuildUri(TokenEndpoint, new NameValueCollection
                {
                    { "code", authorizationCode },
                    { "client_id", _appId },
                    { "client_secret", _appSecret },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                });

            var webRequest = (HttpWebRequest)WebRequest.Create(uri);
            string accessToken = null;
            HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

            // handle response from FB 
            // this will not be a url with params like the first request to get the 'code'
            Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);

            using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
            {
                var serializer = new JavaScriptSerializer();
                var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
                var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));

                Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
                accessToken = desirializedJsonObject["access_token"].ToString();
            }
            return accessToken;
        }

        private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
        {
            var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
            var qs = String.Join("&", keyValuePairs);

            var builder = new UriBuilder(baseUri) { Query = qs };
            return builder.Uri;
        }

        /// <summary>
        /// Facebook works best when return data be packed into a "state" parameter.
        /// This should be called before verifying the request, so that the url is rewritten to support this.
        /// </summary>
        public static void RewriteRequest()
        {
            var ctx = HttpContext.Current;

            var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
            if (stateString == null || !stateString.Contains("__provider__=facebook"))
                return;

            var q = HttpUtility.ParseQueryString(stateString);
            q.Add(ctx.Request.QueryString);
            q.Remove("state");

            ctx.RewritePath(ctx.Request.Path + "?" + q);
        }
    }
    public class OAuthConfig
    {
        public static void RegisterAuth()
        {
            OAuthWebSecurity.RegisterClient(new FacebookClient2017("appId", "appSecret"));
        }
    }
public ActionResult LoginCallback(string error)
{
            FacebookClient2017.RewriteRequest();
            string returnUrl = this.Request.QueryString["ReturnUrl"];
             
            // after that continue as usual

            result = OAuthWebSecurity.VerifyAuthentication(this.Url.Action("LoginCallback", new { ReturnUrl = returnUrl }));

from dotnetopenauth.

adoconnection avatar adoconnection commented on July 21, 2024 2

sort of critical,

all sites with Facebook auth no longer working!

from dotnetopenauth.

ycadieux avatar ycadieux commented on July 21, 2024

Having the same issue here.

Anybody has a quick solution?

from dotnetopenauth.

infinith4 avatar infinith4 commented on July 21, 2024

When will this modified code merge a release module?

from dotnetopenauth.

Corobori avatar Corobori commented on July 21, 2024

I am facing the same issue, hope this code will be merged soon too.

@adoconnection I am trying to translate your code to vb.net but I am facing an issue with
public FacebookClient2017(string appId, string appSecret) : this(appId, appSecret, new[] { "email" }) { }
Any idea how to translate it in vb.net ? Telerik's converter gives me
Public Sub New(appId As String, appSecret As String) Me.New(appId, appSecret, New () {"email"}) End Sub

But this doesn't compile.

from dotnetopenauth.

umair-me avatar umair-me commented on July 21, 2024

Thanks for the solution, worked for me !

from dotnetopenauth.

Hallmanac avatar Hallmanac commented on July 21, 2024

Just a heads up for anyone who comes across this thread like I did and discover that it's not quite working with the current version of the Facebook API (i.e. v3.2 as of this comment). Using the scope parameter did not return an email address for me so I had to resort to using the fields query param when getting the User's data.

That's not to say the above solution was not awesome. It got me 90% there so thank you for that!

Below is the slightly refactored class I came up with. Hopefully this prevents someone else from spinning their wheels trying to figure out why email wouldn't come back :-) :

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;

using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;

using Newtonsoft.Json;


namespace iLSNetwork.Shared.Services.UserAccount
{
    public class FacebookClient2018 : OAuth2Client
    {
        /// <summary>
        /// The app id.
        /// </summary>
        private readonly string _appId;

        /// <summary>
        /// The app secret.
        /// </summary>
        private readonly string _appSecret;

        /// <summary>
        /// The requested scopes.
        /// </summary>
        private readonly string[] _requestedScopes;

        /// <summary>
        /// The requested fields to return from the user profile.
        /// </summary>
        private readonly string[] _userProfileFields;

        /// <summary>
        /// The authorization endpoint.
        /// </summary>
        private readonly string _authorizationEndpoint;

        /// <summary>
        /// The token endpoint.
        /// </summary>
        private readonly string _tokenEndpoint;

        /// <summary>
        /// The user info endpoint.
        /// </summary>
        private readonly string _userInfoEndpoint;


        /// <summary>
        /// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
        /// </summary>
        /// <param name="appId">The Facebook App Id</param>
        /// <param name="appSecret">The Facebook App Secret</param>
        /// <param name="facebookVersion">Version of the Facebook graph api being accessed. This should come in the format of "vX.Y" (i.e. "v3.2"). Default is "v3.2"</param>
        public FacebookClient2018(string appId, string appSecret, string facebookVersion = "v3.2") : this(appId, appSecret, new[] {"email"}, new[] {"email", "name"}, facebookVersion) { }


        /// <summary>
        /// Creates a new Facebook OAuth2 client.
        /// </summary>
        /// <param name="appId">The Facebook App Id</param>
        /// <param name="appSecret">The Facebook App Secret</param>
        /// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
        /// <param name="userProfileFields">One or more requested default user profile fields from facebook. These are limited to "first_name", "last_name", "middle_name", "name", "name_format", "picture", "short_name"</param>
        /// <param name="facebookVersion">Version of the Facebook graph api being accessed. This should come in the format of "vX.Y" (i.e. "v3.2"). Default is "v3.2"</param>
        public FacebookClient2018(string appId, string appSecret, string[] requestedScopes, string[] userProfileFields, string facebookVersion = "v3.2") : base("facebook")
        {
            if (string.IsNullOrWhiteSpace(appId))
                throw new ArgumentNullException(nameof(appId));

            if (string.IsNullOrWhiteSpace(appSecret))
                throw new ArgumentNullException(nameof(appSecret));

            if (requestedScopes == null)
                throw new ArgumentNullException(nameof(requestedScopes));

            if (requestedScopes.Length == 0)
                throw new ArgumentException("One or more scopes must be requested.", nameof(requestedScopes));

            _appId = appId;
            _appSecret = appSecret;
            _requestedScopes = requestedScopes;
            _userProfileFields = userProfileFields ?? new string[0];
            var facebookVersion1 = facebookVersion;

            _authorizationEndpoint = $"https://www.facebook.com/{facebookVersion1}/dialog/oauth";
            _tokenEndpoint = $"https://graph.facebook.com/{facebookVersion1}/oauth/access_token";
            _userInfoEndpoint = $"https://graph.facebook.com/{facebookVersion1}/me";
        }


        public override void RequestAuthentication(HttpContextBase context, Uri returnUrl)
        {
            var redirectUrl = GetServiceLoginUrl(returnUrl).AbsoluteUri;
            context.Response.Redirect(redirectUrl, endResponse: true);
        }


        public new AuthenticationResult VerifyAuthentication(HttpContextBase context)
        {
            throw new NoNullAllowedException();
        }


        public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
        {
            var code = context.Request.QueryString["code"];
            if (string.IsNullOrEmpty(code))
            {
                return AuthenticationResult.Failed;
            }

            var accessToken = QueryAccessToken(returnPageUrl, code);
            if (accessToken == null)
            {
                return AuthenticationResult.Failed;
            }

            var userData = GetUserData(accessToken);
            if (userData == null)
            {
                return AuthenticationResult.Failed;
            }

            var id = userData["id"];
            string name;

            // Some oAuth providers do not return value for the 'username' attribute. 
            // In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id'
            if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name))
            {
                name = id;
            }

            // add the access token to the user data dictionary just in case page developers want to use it
            userData["accesstoken"] = accessToken;

            return new AuthenticationResult(
                isSuccessful: true, provider: ProviderName, providerUserId: id, userName: name, extraData: userData);
        }


        public string QueryAccessTokenByCode(Uri returnUrl, string authorizationCode)
        {
            return QueryAccessToken(returnUrl, authorizationCode);
        }


        /// <summary>
        /// Facebook works best when return data be packed into a "state" parameter.
        /// This should be called before verifying the request, so that the url is rewritten to support this.
        /// </summary>
        public static void RewriteRequest()
        {
            var ctx = HttpContext.Current;

            var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
            if (stateString == null || !stateString.Contains("__provider__=facebook"))
                return;

            var q = HttpUtility.ParseQueryString(stateString);
            q.Add(ctx.Request.QueryString);
            q.Remove("state");

            ctx.RewritePath(ctx.Request.Path + "?" + q);
        }


        private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
        {
            var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
            var qs = string.Join("&", keyValuePairs);

            var builder = new UriBuilder(baseUri) {Query = qs};
            return builder.Uri;
        }


        protected override Uri GetServiceLoginUrl(Uri returnUrl)
        {
            var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);

            return BuildUri(_authorizationEndpoint, new NameValueCollection
            {
                {"client_id", _appId},
                {"scope", $"{string.Join(" ", _requestedScopes)}"},
                {"redirect_uri", returnUrl.GetLeftPart(UriPartial.Path)},
                {"state", state}
            });
        }


        protected override IDictionary<string, string> GetUserData(string accessToken)
        {
            var uri = BuildUri(_userInfoEndpoint, new NameValueCollection {{"access_token", accessToken}, {"fields", string.Join(",", _userProfileFields)}});

            var webRequest = (HttpWebRequest) WebRequest.Create(uri);

            using (var webResponse = webRequest.GetResponse())
                using (var stream = webResponse.GetResponseStream())
                {
                    if (stream == null)
                        return null;

                    using (var textReader = new StreamReader(stream))
                    {
                        var json = textReader.ReadToEnd();
                        var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
                        var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());

                        data.Add("picture", $"https://graph.facebook.com/{data["id"]}/picture");

                        return data;
                    }
                }
        }


        protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
        {
            var uri = BuildUri(_tokenEndpoint, new NameValueCollection
            {
                {"code", authorizationCode},
                {"client_id", _appId},
                {"client_secret", _appSecret},
                {"redirect_uri", returnUrl.GetLeftPart(UriPartial.Path)}
            });

            var webRequest = (HttpWebRequest) WebRequest.Create(uri);
            string accessToken;
            var response = (HttpWebResponse) webRequest.GetResponse();

            // handle response from FB 
            // this will not be a url with params like the first request to get the 'code'
            var rEncoding = Encoding.GetEncoding(response.CharacterSet ?? throw new InvalidOperationException("Response from getting the facebook access token was invalid."));

            using (var sr = new StreamReader(response.GetResponseStream() ?? throw new InvalidOperationException("Response from getting the facebook access token was invalid"), rEncoding))
            {
                var serializer = new JavaScriptSerializer();
                var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
                var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));

                var deserializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
                accessToken = deserializedJsonObject["access_token"].ToString();
            }

            return accessToken;
        }
    }
}

from dotnetopenauth.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.