Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 900
Rasmus Christensen Avatar asked Sep 26 '12 21:09

Rasmus Christensen


2 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!

like image 105
Varun Chatterji Avatar answered Oct 05 '22 02:10

Varun Chatterji


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

like image 21
PussInBoots Avatar answered Oct 05 '22 02:10

PussInBoots