• Authenticating a Canvas Page in Facebook with .Net

    So, the first time I did this was a while back and I remembered it being very confusing.

    Here's the basic breakdown:

     First you need to configure your settings on the app.

     

     

    Note: Canvas Callback URL is where you are going to create a page (in my case Default.aspx) for facebook to connect to.

     

    Second:

    Make an asp.net website. As above mine is called “FacebookApps”. Then I made a folder for the “jpeckham” facebook app. Then a folder called Canvas. Then I put a new webform called Default.aspx in that folder. Looks like this:

     

     

    Third:

    Setup your REST service client to have these operations in the service contract for WCF.

         [OperationContract]

            [WebGet(

                UriTemplate =

            "?method=Users.getLoggedInUser&api_key={apiKey}&sig={sig}&v=1.0&call_id={callId}&session_key={sessionKey}"

                ,

                BodyStyle = WebMessageBodyStyle.Bare)]

            Message getLoggedInUser(string apiKey, string sig, string callId, string sessionKey);

     

            [OperationContract]

            [WebGet(UriTemplate = "?method=Auth.getSession&api_key={apiKey}&sig={sig}&v=1.0&auth_token={authToken}",

                BodyStyle = WebMessageBodyStyle.Bare)]

            Message getSession(string apiKey, string sig, string authToken);

     

    Lastly:

    Do some .net/c# code in the webpage that is something like this:

    string apiKey = "1f3b0f29b995519b003f0fe236d56907";

            Facebook.Service.FacebookCustomProxy proxy = new Facebook.Service.FacebookCustomProxy("FacebookClient",apiKey);

          

            if (string.IsNullOrEmpty(Request.QueryString["fb_sig_session_key"]) && string.IsNullOrEmpty(Request.QueryString["auth_token"]))

            {

                Response.Redirect("http://www.facebook.com/login.php?v=1.0&api_key="+apiKey);

            }

            else

            if (!string.IsNullOrEmpty(Request.QueryString["auth_token"]))

            {

                Response.Write(proxy.getSession(Request.QueryString["auth_token"]).ToString());

            }

            else

            {

                Dictionary<string, string> incomingParmsFromFacebook = new Dictionary<string, string>();

                foreach (string s in Request.QueryString.Keys)

                {

                    if (s.StartsWith("fb_sig_"))

                    {

                        incomingParmsFromFacebook.Add(s, Request.QueryString[s]);

                    }

                }

                if (FacebookCustomProxy.IsValidSigFromFacebook(Request.QueryString["fb_sig"],incomingParmsFromFacebook))

                {

     

                    Response.Write("Your User Id Is: " + proxy.getLoggedInUser(Request.QueryString["fb_sig_session_key"]));

                }

                else

                {

                    Response.Write("Invalid Signature, Access Denied");

                }

            }

     

    I’ll summarize briefly. Basically check to see if you have either a session key or an auth token. If you don’t… kick them out to the login page to authenticate. If they do have an auth token, they just came from the login page so retrieve their brand new session from the API. Finally, if you do have a sessionkey being passed to you from somewhere, check to see if it’s from facebook (see facebook wiki for info on validating sigs from facebook) and then once verified go ahead and make calls using that session key.

     

    I haven’t exactly figured out why a session key is less trusted than an auth_token yet… but apparently they don’t do any sig for an auth token query string. Go figure.

     

     

     

     

     

     

     

     

     

     

    Full story

    Comments (0)

  • Calling RESTful Facebook services with WCF

     

    So, I got some crazy idea that I wanted to take another stab at writing a facebook application. A friend at work was showing off his WPF app that utilizes Netflix webservices and it gave me an idea about a facebook app. Unfortunately, someone already wrote my idea (http://apps.facebook.com/mynetflix ).

     

    I got the idea of how to use wcf from this twitter app example on Kirk Evans Blog

    (here's the code class library minus the config file Download Here)

    <?xml version="1.0" encoding="utf-8" ?>

    <configuration>

      <system.serviceModel>

        <bindings>

          <webHttpBinding>

            <binding name="FacebookConfig">

              <security mode="None"/>

            </binding>

          </webHttpBinding>

        </bindings>

        <client>

          <endpoint

            address="http://api.facebook.com/restserver.php"

            binding="webHttpBinding"

            bindingConfiguration="FacebookConfig"

            contract="Facebook.Service.IFacebookService"

            name="FacebookClient" />

        </client>

      </system.serviceModel>

    </configuration>

     

     My service contract ended up looking like this after a few times banging on my head getting an error that said “No POST data to POST” when trying WebInvoke Attribute.

        [ServiceContract(Namespace=http://api.facebook.com/1.0/)]

        [XmlSerializerFormat(Style= OperationFormatStyle.Document,Use=OperationFormatUse.Literal)]

     

     

        public interface IFacebookService

        {

            [OperationContract]

              [WebGet(

                UriTemplate="?method=admin.getBannedUsers&api_key={apiKey}&sig={sig}&v=1.0",

                BodyStyle=WebMessageBodyStyle.Bare)]

            Message getBannedUsers(string apiKey, string sig);

     

            [OperationContract]

              [WebGet(

                UriTemplate="?method=admin.banUsers&api_key={apiKey}&sig={sig}&v=1.0&uids={userIds}",

                BodyStyle=WebMessageBodyStyle.Bare)]

            Message banUsers(string apiKey, string sigstring userIds);

     

            [OperationContract]

            [WebGet(

                UriTemplate="?method=admin.unbanUsers&api_key={apiKey}&sig={sig}&v=1.0&uids={userIds}",

                BodyStyle=WebMessageBodyStyle.Bare

                )]

            Message unbanUsers(string apiKey, string sig, string userIds);

     

        }

     

    I chose these 3 methods because they do not require a session so I can just pass in my API key without needing a facebook signon to test my mad experiments.

     

    So let’s move on to actually using this thing and the speed bumps I ran into there.

     

    This is a work in progress, so far I’ve only done the “admin.unbanUsers” call

      public class FacebookCustomProxy

        {

            private readonly WebChannelFactory<IFacebookService> channelFactory;

            private readonly IFacebookService facebookChannel;

     

            public FacebookCustomProxy(string endpointName)

            {

                channelFactory =

                    new WebChannelFactory<IFacebookService>(endpointName);

                facebookChannel = channelFactory.CreateChannel();

            }

     

            [Obsolete]

            public Message getBannedUsers(string apiKey, string sig)

            {

                return facebookChannel.getBannedUsers(apiKey, sig);

            }

            [Obsolete]

            public Message banUsers(string apiKey, string sig, string userIds)

            {

                return facebookChannel.banUsers(apiKey, sig, userIds);

            }

     

            public Message unbanUsers( long[] userIds)

            {

                Dictionary<string, string> args = new Dictionary<string, string>();

                args.Add("method", "admin.unbanUsers");

                args.Add("api_key", "<INSERT YOUR API_KEY HERE>");

                args.Add("uids",JasonMe(userIds));

                args.Add("v","1.0");

                string sig = MakeSig(args);

                return facebookChannel.unbanUsers(args["api_key"], sig, args["uids"]);

            }

     

            string JasonMe(long[] arrayOfLongs)

            {

                StringBuilder builder = new StringBuilder("[");

                bool adComma = false;

                foreach (long val in arrayOfLongs)

                {

                    if (adComma)

                    { builder.Append(","); }

                    builder.Append(val.ToString());

                    adComma = true;

                }

                builder.Append("]");

                return builder.ToString();

            }

     

     

             string MakeSig(IDictionary<string, string> parameters)

            {

                StringBuilder signatureBuilder = new StringBuilder();

     

                // Sort the keys of the method call in alphabetical order

                List<string> keyList = new List<string>(parameters.Keys);

                keyList.Sort();

     

                // Append all the parameters to the signature input paramaters

                foreach (string key in keyList)

                    signatureBuilder.Append(String.Format(CultureInfo.InvariantCulture, "{0}={1}", key, parameters[key]));

     

                // Append the secret to the signature builder

                signatureBuilder.Append("<INSERT YOUR APP SECRET HERE>");

     

                MD5 md5 = MD5.Create();

                // Compute the MD5 hash of the signature builder

                byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(signatureBuilder.ToString().Trim()));

     

                // Reinitialize the signature builder to store the actual signature

                signatureBuilder = new StringBuilder();

     

                // Append the hash to the signature

                foreach (byte hashByte in hash)

                    signatureBuilder.Append(hashByte.ToString("x2", CultureInfo.InvariantCulture));

     

                return signatureBuilder.ToString();

            }

        }

     

     

    The first thing I ran into was I tried to get back just a string or a specific strongly typed object. But I kept running into problems with that. One, I couldn’t deserialize into a string, and two, sometimes I would get back <admin_unbanUsers_response> but sometimes I’d get back <error_response>. So I ended up using System.ServiceModel.Channels.Message class.

     

    The second thing I hit was getting “Invalid Sig” errors from facebook.php. props for the MakeSig method to midowazzan on this facebook forum post

     

    Lastly, I had issues because I wasn’t sending a JSON array to facebook. Unfortunately facebook doesn’t use a regular JSON array like this: {“user ids” : [378173,738127]} they use one that looks like this “[47387483,4783743]”, which is ok, just strange and you have no way of knowing without pouring through the awful wiki site.

     

     

    Finally the code to actually execute this one method becomes trivial:

         FacebookCustomProxy proxy = new FacebookCustomProxy("FacebookClient");

               

                Message msg = proxy.unbanUsers( new long[]{789789,12344});

               

                string result = msg.ToString();

               

               

                Console.WriteLine(result);

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Full story

    Comments (3)