How to decode OAuth 2.0 for Canvas signed_request in C#?

I'm able to successfully validate the signed request for a Facebook canvas app using the example here, but I'm unable to decode the payload. The Facebook documentation states that the 2nd parameter in signed_request is a base64url encoded JSON object. In PHP the payload is decoded using json_decode:

$data = json_decode(base64_url_decode($payload), true);

What is the equivalent in C#?

Answers


The following should help you out..

(Note: The JObject reference is from JSON.NET available via http://james.newtonking.com/projects/json-net.aspx and http://json.codeplex.com/)

Namespaces used:

using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json.Linq; // JSON.NET project 

Code:

public Dictionary<string,string> DecodePayload(string payload)
    {
        var encoding = new UTF8Encoding();
        var decodedJson = payload.Replace("=", string.Empty).Replace('-', '+').Replace('_', '/');
        var base64JsonArray = Convert.FromBase64String(decodedJson.PadRight(decodedJson.Length + (4 - decodedJson.Length % 4) % 4, '='));
        var json = encoding.GetString(base64JsonArray);
        var jObject = JObject.Parse(json);

        var parameters = new Dictionary<string, string>();
        parameters.Add("user_id", (string)jObject["user_id"] ?? "");
        parameters.Add("oauth_token", (string)jObject["oauth_token"] ?? "");
        var expires = ((long?) jObject["expires"] ?? 0);
        parameters.Add("expires", expires > 0 ? expires.ToString() : "") ;
        parameters.Add("profile_id", (string)jObject["profile_id"] ?? "");

        return parameters;
    }

It is what I'm using in FaceSharp.. hope it helps


Same code but without Json.NET dependency:

public IDictionary<string, object> DecodePayload(string payload)
{
    string base64 = payload.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=')
        .Replace('-', '+').Replace('_', '/');
    string json = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
    return (IDictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
}

You can use it like this:

public ActionResult Index()
{
    var dict = DecodePayload(Request["signed_request"].Split('.')[1]);
    return Content("Access Token: " + (string)dict["oauth_token"]);
}

Sorry, bit of a StackOverflow noob, but for anyone trying to use JohnK's method to decode, it works brilliantly, just a couple of implementation tips for anyone like me and the others with the base64 encoding issue....

The Json reference is also available from nuGet

Install-Package Newtonsoft.Json

http://developers.facebook.com/docs/guides/canvas/#auth explains the ["signed_request"] element in more detail, but put simply, when Facebook posts back (in my case after a user registration request), you can get the data from the post, but the string is in TWO PARTS, separated by a '.' - As such, trying to decode ["signed_request"] will fail as '.' isn't a Base64 char. The first part is the signature to allow you to validate that the post came from Facebook (only us and them know the sig to decode) and the second is the payload.

So, I got this to work with the following code (in a MVC controller), source is a Facebook registration button....

<fb:registration fields="name,email"  redirect-uri="http://dev.devurlgoeshere.co.uk/Account/Register" width="530">
</fb:registration>

and then the Controller code responds to the registration request

   [HttpPost]
    public ActionResult Register(object postData )
    {
        string requestData = Request.Form["signed_request"];
        string[] splitPayload = requestData.Split('.');
        string sig = splitPayload[0];
        string payload = splitPayload[1];
        var decodedObj = DecodePayload(payload);
        // get the items from the decodedObject
        string userFacebookID = decodedObj["user_id"];
        // now do what you want with their FacebookID
        return View();
    }

hope this helps someone, and sorry if this should have been edit/feedback or whatever...


Check out the Facebook .Net SDK on Codeplex http://facebooksdk.codeplex.com. It will handle all the 'dirty work' for you. For example, I could call the following code either from a controller action or on Page_Load.

FacebookApp app = new FacebookApp();
string accessToken = app.Session.AccessToken;
long userId = app.UserId;

Thats it. You don't really need to worry about how facebook is returning the data to you or decoding it. The SDK handles all that for you.


i have change the DecodePayload by this and it work fine for me:

    public Dictionary<string, string> DecodePayload(string payload) 
    {
        //Remove the bad part of signed_request
        //Begin
        string[] sB64String = payload.Split('.');
        payload = payload.Replace((sB64String[0] + "."), string.Empty);
        //End
        var encoding = new UTF8Encoding(); 
        var decodedJson = payload.Replace("=", string.Empty).Replace('-', '+').Replace('_', '/'); 
        var base64JsonArray = Convert.FromBase64String(decodedJson.PadRight(decodedJson.Length + (4 - decodedJson.Length % 4) % 4, '=')); 
        var json = encoding.GetString(base64JsonArray); 
        var jObject = JObject.Parse(json); 
        var parameters = new Dictionary<string, string>(); 
        parameters.Add("user_id", (string)jObject["user_id"] ?? ""); 
        parameters.Add("oauth_token", (string)jObject["oauth_token"] ?? ""); 
        var expires = ((long?)jObject["expires"] ?? 0); 
        parameters.Add("expires", expires > 0 ? expires.ToString() : ""); 
        parameters.Add("profile_id", (string)jObject["profile_id"] ?? ""); 
        return parameters; 
    }

Here's how to do it using Facebook SDK

var parsedSignedRequest = FacebookSignedRequest.Parse(FacebookApplication.Current, signed_request);

Need Your Help

Convert date from milliseconds to ISODate object

javascript mongodb type-conversion aggregation-framework

I am trying to aggregate records in a MongoDB collection by hour and need to convert date stored as timestamp (milliseconds) to ISODate so that I can use aggregate framework's built-in date operato...

Entity Framework 6 with SQLite 3 Code First - Won't create tables

.net sqlite ef-code-first entity-framework-6 system.data.sqlite

Using latest versions of EF6 and SQLite from NuGet. I finally got the app.config file to work after some useful posts on Stackoverflow. Now the problem is that the tables are not being created alth...