Rowan Freeman
Rowan Freeman

Reputation: 16358

How do I use a Facebook signed_request in .NET?

I'm using Facebook as a login provider for my web application (ASP.NET MVC).

My login works similar to another StackOverflow post How to securely authorize a user via Facebook's Javascript SDK. I also share the user's concerns.

The flow for my login is as Follows:

1. The user presses the login button.

Log in with Facebook

2. The user must accept the app.

Accepting the app

3. A javascript callback retrieves the response.

var authResponse = response.authResponse;

Object returned:

{
    accessToken: "...",
    expiresIn: 1234,
    signedRequest: "...",
    userID: "123456789"
}

I've heard that I can used the signed_request to validate the user's request, but all the examples online are for PHP. How do I do this in .NET?

Upvotes: 5

Views: 1077

Answers (2)

Doug S
Doug S

Reputation: 10315

To compile Rowan's answer into its final code:

public static string DecodeSignedRequest(string signed_request)
{
    try
    {
        if (signed_request.Contains("."))
        {
            string[] split = signed_request.Split('.');

            string signatureRaw = FixBase64String(split[0]);
            string dataRaw = FixBase64String(split[1]);

            // the decoded signature
            byte[] signature = Convert.FromBase64String(signatureRaw);

            byte[] dataBuffer = Convert.FromBase64String(dataRaw);

            // JSON object
            string data = Encoding.UTF8.GetString(dataBuffer);

            byte[] appSecretBytes = Encoding.UTF8.GetBytes(app_secret);
            System.Security.Cryptography.HMAC hmac = new System.Security.Cryptography.HMACSHA256(appSecretBytes);
            byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(split[1]));
            if (expectedHash.SequenceEqual(signature))
            {
                return data;
            }
        }
    }
    catch
    {
        // error
    }
    return "";
}

private static string FixBase64String(string str)
{
    while (str.Length % 4 != 0)
    {
        str = str.PadRight(str.Length + 1, '=');
    }
    return str.Replace("-", "+").Replace("_", "/");
}

Thanks Rowan!

Upvotes: 5

Rowan Freeman
Rowan Freeman

Reputation: 16358

Yes, the signed_request can be used to verify that an incoming login request is genuine. If you're logging in a user with Javascript (via AJAX, for example) you can use the signed_request to ensure that the data isn't false.

According to Parsing the Signed Request, there are 3 major steps, however I'll be a little more specific.

  1. Take the signed_request string and split it into two strings. There is a period character (full stop) which is a delimiter.
    • The first part of the string (the signature) is a hash of the second part.
    • The second part contains some information about the user and the request (user ID, timestamp).
  2. The strings are in Base64, but cannot be decoded straight away.
    • They are Base64-URL-encoded which means that + and / characters have been replaced with URL-friendly - and _ characters. Replace - characters with + and _ characters with /.
    • The strings may not be fully Base64 padded. Base64 strings should be divisible by 4; pad the strings out as necessary.
  3. Hash the signature using HMAC (SHA256) using your app secret as the key and compare the result to the signature that was provided.

1. Split and decode

Code

string response = ""; // the signed_request

string[] split = response.Split('.');

string signatureRaw = FixBase64String(split[0]);
string dataRaw = FixBase64String(split[1]);

// the decoded signature
byte[] signature = Convert.FromBase64String(signatureRaw);

byte[] dataBuffer = Convert.FromBase64String(dataRaw);

// JSON object
string data = Encoding.UTF8.GetString(dataBuffer);

FixBase64String()

static string FixBase64String(string str)
{
    string result = str;

    while (result.Length % 4 != 0)
    {
        result = result.PadRight(result.Length + 1, '=');
    }

    result = result.Replace("-", "+").Replace("_", "/");

    return result;
}

2. Compare the hashes

byte[] appSecretBytes = Encoding.UTF8.GetBytes("my_app_secret_here");

HMAC hmac = new HMACSHA256(appSecretBytes);

byte[] expectedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(dataRaw));

bool areEqual = expectedHash.SequenceEqual(signature);

If areEqual is true then you can be sure that the signed request is valid and has not been tampered with (assuming your app secret is secure).

Remember to keep your app secret secure, otherwise malicious users can do bad things.

Upvotes: 4

Related Questions