Using Instagram API To Output Profile Photos In ASP.NET (2020 Edition)

Don’t you love the good ol’ days back when querying API’s provided by social platforms was a straight forward process and at a time when it felt like there were fewer barriers for entry. This thought came to mind when I had to use Instagram’s API feed again recently to output a list of photos for one of my personal projects.

The last time I carried out any interaction with Instagram's API was back in 2013 where it was a much more simpler affair. All that was required is to generate an access token that never expired, which could then be used against any API endpoint to get data back from Instagram.

Reading Jamie Maguires really useful three-part blog post on tapping into Instagrams API gave me a good foundation into how the API has changed since I used it last. But the example he used required the profile you wanted to interact with had to be set up as a business user. However, interacting with Instagram’s API requires a lot more effort if you (like most users) do not have a business profile.

As far as I understand it, if you plan on making any interaction with Instagrams API as a non-business user the process is:

  1. Create Facebook Developer App
  2. Authenticate application by logging into the Instagram account. This will then generate a short-lived token valid for 1 hour.
  3. Exchange the short-lived token for a long-lived token that is valid for 60 days. This token will need to be stored somewhere at the application level.
  4. Ensure the long-lived token is refreshed before it expires within the 60-day window.

It is very important that we are using the long-lived token and to continually renew it by having some form of background process that carries out this check, whether this is at application or Azure level. We can then use this token to make queries to Instagram API endpoints.

In this post, I am going to perform very simple integration to output a list of images from an Instagram profile. By demonstrating this, we should then get a fair idea on how to interact with the other API endpoints. Personally, setting everything up the process to acquire an access-token is the part that requires the most effort. So let's get to it!

Create a Facebook Developer Application

Since Facebook has taken over Instagram, naturally the application process starts within Facebook's developer site, which can be found at: https://developers.facebook.com/apps/. Once you have logged in using your Facebook credentials, the following steps will need to be carried out:

  • Select "Add New App". In the popup, enter the application name and contact email.
  • You will be presented with a list of products. Select "Instagram" by pressing the "Setup" button.
  • From the left-hand navigation, go to: Settings > Basic. Scroll to the bottom and click on the "Add platform" button. From the popup, select "Website".
  • Enter the website URL. For testing, this will be the URL we set up in the previous section. Ensure this URL is prefixed with https://. Click the "Save" button.
  • Again, from the left-hand navigation (under Products > Instagram), select: "Basic Display". At the bottom of the page, click the "Create New App" button.
  • Enter the following fields (based on our test domain):
  • Valid OAuth Redirect URIs: https://myinstagramapp.surinderbhomra.com/Instagram/Auth
  • Deauthorize Callback URL: https://myinstagramapp.surinderbhomra.com
  • Data Deletion Requests: https://myinstagramapp.surinderbhomra.com
  • Add an Instagram Test user. This can be the clients Instagram profile name.
  • Add instagram_graph_user_media permission, so we can read the profile images.
  • Click "Save Changes" button.

You will have noticed I have added website URL’s for the OAuth Redirect, Deauthorize Redirect and Deletion Request fields. As these fields are required, you can enter the dummy website URL for local development purposes. Just remember to change this when you move the application to the live domain. In the meantime to utilise those dummy URL’s, your local host file will need to be updated. Please refer to a post I wrote in 2012 for further information. It might be over 8 years old, but the process is the same even if the Facebook Developer interface has changed.

Once the Developer Application has been set up, grab the App ID and App Secret to be used in our demo application.

Photo Feed Application

The Photo Feed application will provide a very simple demonstration of the authentication process and interacting with the API to get back our media objects. From here, you will have the tools to improve and expand on this to delve deeper into other API endpoints Instagram has to offer.

To start, we have two helper classes that the application will rely on:

  1. InstagramAuthProvider
  2. InstagramMediaProvider

InstagramAuthProvider

The InstagramAuthProvider carries out all authentication processes for acquiring both short and long-lived tokens.

public class InstagramAuthProvider
{
    #region Json Response Objects

    public class AuthenticateRequest
    {
        [JsonProperty("error_type")]
        public string ErrorType { get; set; }

        [JsonProperty("code")]
        public int StatusCode { get; set; }

        [JsonProperty("error_message")]
        public string ErrorMessage { get; set; }

        [JsonProperty("access_token")]
        public string AccessToken { get; set; }

        [JsonProperty("user_id")]
        public long UserId { get; set; }
    }

    public class LongLivedTokenRequest
    {
        [JsonProperty("access_token")]
        public string AccessToken { get; set; }

        [JsonProperty("token_type")]
        public string TokenType { get; set; }

        [JsonProperty("expires_in")]
        public long ExpiresInSeconds { get; set; }
    }

    #endregion

    /// <summary>
    /// Carries out initial authentication approach after user had approved app to Instagram account link.
    /// Returns a short-lived token valid for 1 hour.
    /// </summary>
    /// <param name="code"></param>
    /// <returns></returns>
    public static async Task<AuthenticateRequest> GetAccessTokenAsync(string code)
    {
        string authResponse = string.Empty;

        if (!string.IsNullOrEmpty(code))
        {
            Dictionary<string, string> parameters = new Dictionary<string, string>
                {
                    { "client_id", ConfigurationManager.AppSettings["Instagram.ClientID"].ToString() },
                    { "client_secret", ConfigurationManager.AppSettings["Instagram.AppSecret"].ToString() },
                    { "grant_type", "authorization_code" },
                    { "redirect_uri", $"{ConfigurationManager.AppSettings[“Site.Domain"]}{ConfigurationManager.AppSettings["Instagram.AuthRedirectPath"]}" },
                    { "code", code }
                };

            FormUrlEncodedContent encodedParameters = new FormUrlEncodedContent(parameters);

            HttpClient client = new HttpClient();

            HttpResponseMessage response = await client.PostAsync("https://api.instagram.com/oauth/access_token", encodedParameters);
            authResponse = await response.Content.ReadAsStringAsync();
        }

        return JsonConvert.DeserializeObject<AuthenticateRequest>(authResponse);
    }

    /// <summary>
    /// Exchanges a short-lived token for a long-lived token that are valid for 60 days.
    /// </summary>
    /// <param name="shortliveAccessToken"></param>
    /// <returns></returns>
    public static async Task<LongLivedTokenRequest> GetLongLifeTokenAsync(string shortliveAccessToken)
    {
        string authResponse = string.Empty;

        if (!string.IsNullOrEmpty(shortliveAccessToken))
        {
            HttpClient client = new HttpClient();

            HttpResponseMessage response = await client.GetAsync($"https://graph.instagram.com/access_token?client_secret={ConfigurationManager.AppSettings["Instagram.AppSecret"].ToString()}&grant_type=ig_exchange_token&access_token={shortliveAccessToken}");
            authResponse = await response.Content.ReadAsStringAsync();
        }

        return JsonConvert.DeserializeObject<LongLivedTokenRequest>(authResponse);
    }

    /// <summary>
    /// Refresh a long-lived Instagram User Access Token that is at least 24 hours old but has not expired.
    /// </summary>
    /// <param name="longLivedAccessToken"></param>
    /// <returns></returns>
    public static async Task<LongLivedTokenRequest> RefreshTokenAsync(string longLivedAccessToken)
    {
        string authResponse = string.Empty;

        if (!string.IsNullOrEmpty(longLivedAccessToken))
        {
            HttpClient client = new HttpClient();

            HttpResponseMessage response = await client.GetAsync($"https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token={longLivedAccessToken}");
            authResponse = await response.Content.ReadAsStringAsync();
        }

        return JsonConvert.DeserializeObject<LongLivedTokenRequest>(authResponse);
    }
}

InstagramMediaProvider

The InstagramMediaProvider returns media information based on the authentication token.

public class InstagramMediaProvider
{
    #region Json Response Objects

    public class MediaCollection
    {
        [JsonProperty("data")]
        public List<MediaInfo> Data { get; set; }
    }

    public class MediaInfo
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("caption")]
        public string Caption { get; set; }

        [JsonProperty("permalink")]
        public string InstagramUrl { get; set; }

        [JsonProperty("media_type")]
        public string Type { get; set; }

        [JsonProperty("thumbnail_url")]
        public string VideoThumbnailUrl { get; set; }

        [JsonProperty("media_url")]
        public string Url { get; set; }
    }

    #endregion

    private string _accessToken;

    public InstagramMediaProvider(string accessToken)
    {
        _accessToken = accessToken;
    }

    /// <summary>
    /// Gets list of all user images.
    /// </summary>
    /// <returns></returns>
    public async Task<List<MediaInfo>> GetUserMedia()
    {
        var mediaInfo = await GetAllMediaAsync();

        if (mediaInfo?.Data.Count > 0)
            return mediaInfo.Data;
        else
            return new List<MediaInfo>();
    }

    /// <summary>
    /// Outputs information about a single media item.
    /// </summary>
    /// <returns></returns>
    private async Task<MediaCollection> GetAllMediaAsync()
    {
        string mediaResponse = string.Empty;

        HttpClient client = new HttpClient();

        HttpResponseMessage response = await client.GetAsync($"https://graph.instagram.com/me/media?fields=id,media_type,media_url,thumbnail_url,permalink,caption,timestamp&access_token={_accessToken}");

        mediaResponse = await response.Content.ReadAsStringAsync();

        if (response.StatusCode != HttpStatusCode.OK)
            return null;

        return JsonConvert.DeserializeObject<MediaCollection>(mediaResponse);
    }
}

Authorisation and Authentication

The authorisation and authentication functionality will be performed in the InstagramController.

public class InstagramController : Controller
{
     /// <summary>
     /// Authorises application with Instagram.
     /// </summary>
     /// <returns></returns>
    public ActionResult Authorise()
    {
        return Redirect($"https://www.instagram.com/oauth/authorize?client_id={ConfigurationManager.AppSettings["Instagram.AppID"].ToString()}&redirect_uri={ConfigurationManager.AppSettings[“Site.Domain"]}{ConfigurationManager.AppSettings["Instagram.AuthRedirectPath"]}&scope=user_profile,user_media&response_type=code");
    }

    /// <summary>
    /// Makes authentication request to create access token.
    /// </summary>
    /// <param name="code"></param>
    /// <returns></returns>
    public async Task<ActionResult> Auth(string code)
    {
        InstagramAuthProvider.AuthenticateRequest instaAuth = await InstagramAuthProvider.GetAccessTokenAsync(code);

        if (!string.IsNullOrEmpty(instaAuth?.AccessToken))
        {
            InstagramAuthProvider.LongLivedTokenRequest longTokenRequest = await InstagramAuthProvider.GetLongLifeTokenAsync(instaAuth.AccessToken);

            if (!string.IsNullOrEmpty(longTokenRequest?.AccessToken))
            {                
               // Storing long-live token in a session for demo purposes. 
               // Store the token in a more permanent place, such as a database.
                Session["InstagramAccessToken"] = longTokenRequest.AccessToken;

                return Redirect("/");
            }
        }

        return Content("Error authenticating.");
    }
}

Authorise

Before we can get any of our tokens, the first step is to get authorisation from Instagram against our web application. So somewhere in the application (preferably not publicly visible), we will need an area that will kick this off. In this case, by navigating to /Instagram/Authorise, will cause the Authorise action in the controller to be fired.

All the Authorise action does is takes you to Instagrams login page and sends over the App ID and Redirect Path. Remember, the Redirect path needs to be exactly as you've set it in your Facebook Developer Application. Once you have successfully logged in, you’ll be redirected back to the application.

NOTE: I’ll be honest here and say I am not sure if there is a better way to acquire the access token as it seems very odd to me that you have to log in to Instagram first. If any of you know of a better way, please leave a comment.

Auth

If the authorise process was successful, you will be redirected back the application and be given an authorisation code. The authorisation code will be parsed to the InstagramAuthProvider.GetAccessTokenAsync() method to be exchanged for our first access-token valid - short-lived valid for 1 hour.

The last step of the process is to now send the short-lived access token to the InstagramAuthProvider.GetLongLifeTokenAsync() method that will carry out a final exchange to retrieve our long-life token valid for 60 days. It is this token we need to store somewhere so we can use it against any Instagram API endpoint.

In my example, the long-life token is stored in a Session for demonstration purposes. In a real-world application, we would want to store this in a database somewhere and have a scheduled task in place that will call the InstagramAuthProvider.RefreshTokenAsync() method every 59 days for the long life token to be renewed for another 60 days.

Photo Feed

Now we come onto the easy part - use the long-live access token to return a list of photos.

public class InstagramMediaController : Controller
{
    /// <summary>
    /// Outputs all user images from Instagram profile.
    /// </summary>
    /// <returns></returns>
    [OutputCache(Duration = 60)]
    public PartialViewResult PhotoGallery()
    {
        if (Session["InstagramAccessToken"] != null)
        {
            InstagramMediaProvider instaMedia = new InstagramMediaProvider(Session["InstagramAccessToken"].ToString());

            return PartialView("_PhotoGallery", Task.Run(() => instaMedia.GetUserMedia()).Result);
        }

        return PartialView("_PhotoGallery", new List<InstagramMediaProvider.MediaInfo>());
    }
}

This controller contains a single piece of functionality - a PhotoGallery partial view. The PhotoGallery partial view uses the InstagramMediaProvider.GetUserMedia() method to return a collection of profile photos.

Final Thoughts

Before carrying out any Instagram integration, think about what you are trying to achieve. Look through the documentation to ensure the API's you require are available as some permissions and features may require business or individual verification to access live data.

Also, you may (or may not) have noticed whilst going through the Facebook Development Application setup that I never submitted the application for review. I would advise you to always submit your application to Facebook as you might find your access is revoked. For personal purposes where you're only outputting your own Instagram profile information, you could just leave it "In development" mode. But again, I do not advise this, especially when working with Business accounts.