OAuthWebSecurity with Facebook not using email permission as expected

Using the new OAuthWebSecurity for authenticating with Facebook, I added the email permission on my Facebook application. Now, as I can read, I need to define a scope to be able to actually get the email in the result. So far without the scope I'm not getting the users' email and am not sure why as I can not see where to define the "scope".

It's just a rip of the ASP.NET MVC 4 default authenticationcontrollers external login.

Answers


Firstly, the extraData parameter is not passed to facebook. It is for internal use only. See the following link on how this data can be used on your site:

http://blogs.msdn.com/b/pranav_rastogi/archive/2012/08/24/customizing-the-login-ui-when-using-oauth-openid.aspx

Now, to the meat:

In addition to the methods RegisterFacebookClient, RegisterYahooClient etc. in OAuthWebSecurity, there is also a generic method RegisterClient. This is the method we will be using for this solution.

This idea germinates from the code provided at: http://mvc4beginner.com/Sample-Code/Facebook-Twitter/MVC-4-oAuth-Facebook-Login-EMail-Problem-Solved.html

However, we will not be using the hacky approach provided by the solution. Instead, we will create a new class called FacebookScopedClient which will implement IAuthenticationClient. Then we will simply register the class using:

OAuthWebSecurity.RegisterClient(new FacebookScopedClient("your_app_id", "your_app_secret"), "Facebook", null);

in AuthConfig.cs

The code for the class is:

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

    public class FacebookScopedClient : IAuthenticationClient
        {
            private string appId;
            private string appSecret;

            private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id=";
            public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?";
            public const string graphApiMe = "https://graph.facebook.com/me?";


            private static string GetHTML(string URL)
            {
                string connectionString = URL;

                try
                {
                    System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString);
                    myRequest.Credentials = CredentialCache.DefaultCredentials;
                    //// Get the response
                    WebResponse webResponse = myRequest.GetResponse();
                    Stream respStream = webResponse.GetResponseStream();
                    ////
                    StreamReader ioStream = new StreamReader(respStream);
                    string pageContent = ioStream.ReadToEnd();
                    //// Close streams
                    ioStream.Close();
                    respStream.Close();
                    return pageContent;
                }
                catch (Exception)
                {
                }
                return null;
            }

            private  IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
            {

                string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" + HttpUtility.UrlEncode(redirectURI) + "&client_secret=" + appSecret + "&code=" + accessCode);
                if (token == null || token == "")
                {
                    return null;
                }
                string data = GetHTML(graphApiMe + "fields=id,name,email,gender,link&access_token=" + token.Substring("access_token=", "&"));

                // this dictionary must contains
                Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
                return userData;
            }

            public FacebookScopedClient(string appId, string appSecret)
            {
                this.appId = appId;
                this.appSecret = appSecret;
            }

            public string ProviderName
            {
                get { return "Facebook"; }
            }

            public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl)
            {
                string url = baseUrl + appId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=email";
                context.Response.Redirect(url);
            }

            public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context)
            {
                string code = context.Request.QueryString["code"];

                string rawUrl = context.Request.Url.OriginalString;
                //From this we need to remove code portion
                rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");

                IDictionary<string, string> userData = GetUserData(code, rawUrl);

                if (userData == null)
                    return new AuthenticationResult(false, ProviderName, null, null, null);

                string id = userData["id"];
                string username = userData["email"];
                userData.Remove("id");
                userData.Remove("email");

                AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
                return result;
            }
        }

now in the

public ActionResult ExternalLoginCallback(string returnUrl)

method in AccountController, result.ExtraData should have the email.

Edit: I missed some code in this post. I am adding it below:

public static class String
    {
        public static string Substring(this string str, string StartString, string EndString)
        {
            if (str.Contains(StartString))
            {
                int iStart = str.IndexOf(StartString) + StartString.Length;
                int iEnd = str.IndexOf(EndString, iStart);
                return str.Substring(iStart, (iEnd - iStart));
            }
            return null;
        }
    }

Cheers!


Update your NuGet package in your MVC4 Internet project.

DotNetOpenAuthCore. It will automatically update all dependencies.

Now result.UserName will contain the email adress instead of your name.

    [AllowAnonymous]
    public ActionResult ExternalLoginCallback(string returnUrl)
    {
        AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
        if (!result.IsSuccessful)
        {
            return RedirectToAction("ExternalLoginFailure");
        }

        if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
        {
            return RedirectToLocal(returnUrl);
        }

        if (User.Identity.IsAuthenticated)
        {
            // If the current user is logged in add the new account
            OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            // User is new, ask for their desired membership name
            string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
            ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
            ViewBag.ReturnUrl = returnUrl;
            return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
        }
    }

The reason for this?

https://github.com/AArnott/dotnetopenid/blob/a9d2443ee1a35f13c528cce35b5096abae7128f4/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs has been updated in latest NuGet package.

The commit with the fix: https://github.com/AArnott/dotnetopenid/commit/a9d2443ee1a35f13c528cce35b5096abae7128f4


I used Varun's answer, but I had to make a small modification to get it to work for my app being hosted on AppHarbor.

AppHarbor has to do some funky stuff with the port number in urls to handle load balancing. You can read a little more about it here. In short, getting the AbsoluteUri of the current request while hosting on AppHarbor may return a uri with a port number other than 80. This causes problems with Facebook authentication, because they expect your return url to be the one you specified when creating your app.

The problem comes in at string rawUrl = context.Request.Url.OriginalString; in VerifyAuthentication(). If you use this code, rawUrl may contain some port number other than 80, causing the Facebook authentication to fail. Instead, replace that line with

string rawUrl = GetRawUrl(context.Request.Url);

and add the GetRawUrl() function to the class:

public static string GetRawUrl(Uri url)
{
    var port = url.Port;
    if (SettingsHelper.GetHostingService() == HostingServices.AppHarbor)
        port = 80;

    return new UriBuilder(url)
    {
        Port = port
    }.Uri.AbsoluteUri;
}

You will need to replace if (SettingsHelper.GetHostingService() == HostingServices.AppHarbor) with your own logic for determining whether or not your application is running on AppHarbor.


I wrote my own solution to this problem. I extended the OAuth2Client to take advantage of it work and used facebook scope and other features to retrieve additional user data. I posted my own solution here, I hope it will help someone!


Since FB made "Use Strict Mode for Redirect URIs" mandatory the RewriteRequest is needed (as for Google Oauth). Add the call below in callback processing before OAuthWebSecurity.VerifyAuthentication.

FacebookScopedClient.RewriteRequest();

FacebookScopedClient Class

using System;
using System.Collections.Generic;
using System.Text;
using DotNetOpenAuth.AspNet;
using System.Web;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using Newtonsoft.Json;

namespace UserAccounts.WebApi.ExternalLogin
{
    // Thnks to Har Kaur https://www.c-sharpcorner.com/blogs/facebook-integration-by-using-oauth and https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2/blob/master/DotNetOpenAuth.GoogleOAuth2/GoogleOAuth2Client.cs
    public class FacebookScopedClient : IAuthenticationClient
    {
        private string appId;
        private string appSecret;
        private static string providerName = "Facebook";

        private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id=";
        public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?";
        public const string graphApiMe = "https://graph.facebook.com/me?";


        private static string GetHTML(string URL)
        {
            string connectionString = URL;

            try
            {
                System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString);
                myRequest.Credentials = CredentialCache.DefaultCredentials;
                //// Get the response  
                WebResponse webResponse = myRequest.GetResponse();
                Stream respStream = webResponse.GetResponseStream();
                ////  
                StreamReader ioStream = new StreamReader(respStream);
                string pageContent = ioStream.ReadToEnd();
                //// Close streams  
                ioStream.Close();
                respStream.Close();
                return pageContent;
            }
            catch (WebException ex)
            {
                StreamReader reader = new StreamReader(ex.Response.GetResponseStream());
                string line;
                StringBuilder result = new StringBuilder();
                while ((line = reader.ReadLine()) != null)
                {
                    result.Append(line);
                }

            }
            catch (Exception)
            {
            }
            return null;
        }


        private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
        {
            string value = "";
            string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" +
                                    HttpUtility.UrlEncode(redirectURI) + "&client_secret=" +
                                       appSecret + "&code=" + accessCode);
            if (token == null || token == "")
            {
                return null;
            }
            if (token != null || token != "")
            {
                if (token.IndexOf("access_token") > -1)
                {
                    string[] arrtoken = token.Replace("\''", "").Split(':');
                    string[] arrval = arrtoken[1].ToString().Split(',');
                    value = arrval[0].ToString().Replace("\"", "");
                }
            }
            string data = GetHTML(graphApiMe + "fields=id,name,email,gender,link&access_token=" + value);


            // this dictionary must contains  
            Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
            return userData;
        }


        public FacebookScopedClient(string appId, string appSecret)
        {
            this.appId = appId;
            this.appSecret = appSecret;
        }

        public string ProviderName
        {
            get { return providerName; }
        }

        public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl)
        {
            var uriBuilder = new UriBuilder(returnUrl);
            uriBuilder.Query = "";
            var newUri = uriBuilder.Uri;

            string returnUrlQuery = HttpUtility.UrlEncode(returnUrl.Query);
            string url = baseUrl + appId + "&scope=email" + "&state=" + returnUrlQuery + "&redirect_uri=" + HttpUtility.UrlEncode(newUri.ToString());
            context.Response.Redirect(url);
        }

        public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context)
        {
            string code = context.Request.QueryString["code"];

            string rawUrl = context.Request.Url.OriginalString;
            //From this we need to remove code portion  
            rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
            var uriBuilder = new UriBuilder(rawUrl);
            uriBuilder.Query = "";
            var newUri = uriBuilder.Uri;

            IDictionary<string, string> userData = GetUserData(code, newUri.ToString());

            if (userData == null)
                return new AuthenticationResult(false, ProviderName, null, null, null);

            string id = userData["id"];
            string username = userData["email"];
            userData.Remove("id");
            userData.Remove("email");

            AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
            return result;
        }

        /// <summary>
        /// Facebook requires that all 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.
        /// Thnks to Matt Johnson mj1856 https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2/blob/master/DotNetOpenAuth.GoogleOAuth2/GoogleOAuth2Client.cs
        /// </summary>
        /// 
        public static void RewriteRequest()
        {
            var ctx = HttpContext.Current;

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

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

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

I faced the same problem here. The only way I found to pass the "scope" parameter to facebook was coding my own OAuth client.

To do it, you must extend and implement the abstract methods of DotNetOpenAuth.AspNet.Clients.OAuth2Client.

On the GetServiceLoginUrl method you can add the scope parameter to the url. So, when you call the OAuthWebSecurity.VerifyAuthentication() method, the AuthenticationResult.UserName provides the user's email.

An example can be found here.

Good luck.


It can be done...like this:

var fb = new Dictionary<string, object>();
fb.Add("scope", "email,publish_actions");
OAuthWebSecurity.RegisterFacebookClient(
appId: ConfigurationManager.AppSettings["FacebookAppId"],
appSecret: ConfigurationManager.AppSettings["FacebookAppSecret"],
displayName: "FaceBook",
extraData: fb);

Need Your Help

Return background color of selected cell

excel vba excel-vba

I have a spreadsheet which cells in are colored meaningfully.

Retrieving parameters from a URL

python django parsing url

Given a URL like the following, how can I parse the value of the query parameters? For example, in this case I want the value of def.