Search code examples
c#.netinstagraminstagram-api

Enforce signed request to Instagram using C#


I am trying to communicate with Instagram API and wish to use Signed Request for secure communication with API as per the link given below

https://instagram.com/developer/secure-api-requests/

At the default page, I simply fetch the details like Client Key, Client Secret Code and Redirect URL and get authenticated by Instagram.

Once authenticated, at redirect URL, following is my code:

//To Get Access Token

var json = "";

 NameValueCollection parameters = new NameValueCollection();
                parameters.Add("client_id", ConfigurationManager.AppSettings["instagram.clientid"].ToString());
                parameters.Add("client_secret", ConfigurationManager.AppSettings["instagram.clientsecret"].ToString());
                parameters.Add("grant_type", "authorization_code");
                parameters.Add("redirect_uri", ConfigurationManager.AppSettings["instagram.redirecturi"].ToString());
                parameters.Add("code", code);

                WebClient client = new WebClient();
                var result = client.UploadValues("https://api.instagram.com/oauth/access_token", "POST", parameters);
                var response = System.Text.Encoding.Default.GetString(result);

                // deserializing nested JSON string to object
                var jsResult = (JObject)JsonConvert.DeserializeObject(response);
                string accessToken = (string)jsResult["access_token"];
                int id = (int)jsResult["user"]["id"];

Page.ClientScript.RegisterStartupScript(this.GetType(), "GetToken", "<script>var instagramaccessid=\"" + @"" + id + "" + "\"; var instagramaccesstoken=\"" + @"" + accessToken + "" + "\";</script>");

After getting Access Token, let us say I get the Popular photos from Instagram. Following is a div to hold the popular photos

 <div style="clear:both;"></div>
        <div>
            <h1>
                Popular Pictures</h1>
            <div id="PopularPhotosDiv">
                <ul id="photosUL1">
                </ul>
            </div>
        </div>

Then I use the following function to fill the div of popular photos

 <script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(document).ready(function () {           
            GetPopularPhotos();
           
        });

function GetPopularPhotos() {
            $("#photosUL1").html("");
            $.ajax({
                type: "GET",
                async: true,
                contentType: "application/json; charset=utf-8",
                //Most popular photos
                url: "https://api.instagram.com/v1/media/popular?access_token=" + instagramaccesstoken,
                dataType: "jsonp",
                cache: false,
                beforeSend: function () {
                    $("#loading").show();
                },
                success: function (data)
                {
                    $("#loading").hide();
                    if (data == "") {
                        $("#PopularPhotosDiv").hide();
                    } else {
                        $("#PopularPhotosDiv").show();
                        for (var i = 0; i < data["data"].length; i++) {
                            $("#photosUL1").append("<li style='float:left;list-style:none;'><a target='_blank' href='" + data.data[i].link + "'><img src='" + data.data[i].images.thumbnail.url + "'></img></a></li>");
                        }

                    }
                }

            });
        }

This code is working fine, I just want to send it as a signed request.


Solution

  • The Enforced Signed Requests option for the Instagram API ensures that only the applications with the client secret can create requests to the API, even if the access token has been leaked. The signature you generate for every request is in theory an unforgeable token that only your application and Instagram API can create and verify.

    The method for creating signatures for the Instagram API is by using the "keyed-hash message authentication code" (HMAC) algorithm HMAC-SHA256. In .NET, you can create HMAC-SHA256 signatures using the HMACSHA256 class. The function requires a message that will be authenticated and a secret key used for this authentication.

    Using the examples on the page you linked to, here's what I came up with in C# to generate these signatures:

    static string GenerateSignature(string endpoint, Dictionary<string,string> parameters, string secret)
    {
        // Build the message to be authenticated
        StringBuilder message = new StringBuilder(endpoint);
        foreach (var param in parameters.OrderBy(p => p.Key))
        {
            message.AppendFormat("|{0}={1}", param.Key, param.Value);
        }
    
        // Create a HMAC-SHA256 digest of the message using the secret key
        HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        byte[] digest = hmac.ComputeHash(Encoding.UTF8.GetBytes(message.ToString()));
    
        // Return the digest as a hexstring to be used as a signature for the request
        return ByteArrayToString(digest);
    }
    
    static string ByteArrayToString(byte[] array)
    {
        // Convert the bytes in the array to a lower-case hexstring
        return array.Aggregate(new StringBuilder(), (sb, b) => sb.Append(b.ToString("x2"))).ToString();
    }
    
    static void Main(string[] args)
    {
        string secret = "6dc1787668c64c939929c17683d7cb74";
        string endpoint;
        Dictionary<string, string> parameters;
        string signature;
    
        // Example 1
        endpoint = "/users/self";
        parameters = new Dictionary<string, string>
        {
            { "access_token", "fb2e77d.47a0479900504cb3ab4a1f626d174d2d" },
        };
        signature = GenerateSignature(endpoint, parameters, secret);
        Console.WriteLine("sig={0}", signature);
    
        // Example 2
        endpoint = "/media/657988443280050001_25025320";
        parameters = new Dictionary<string, string>
        {
            { "access_token", "fb2e77d.47a0479900504cb3ab4a1f626d174d2d" },
            { "count", "10" },
        };
        signature = GenerateSignature(endpoint, parameters, secret);
        Console.WriteLine("sig={0}", signature);
    
        Console.ReadKey(true);
    }
    

    The generated signatures should match the ones given in the documentation examples.

    As Instagram's documentation warns, never make this client secret key publically available. In other words, make sure the client secret won't be distributed on mobile devices, website JavaScript, desktop applications, etc. The key should always be kept secret between you and Instagram.