Integrating "Sign In With LinkedIn" Functionality To An Application

Introduction

This article demonstrates how to integrate "Log in using LinkedIn" functionality to your web application. There are four ways to integrate LinkedIn - REST API, JavaScript SDK, Android SDK, and iOS SDK. Here, I will explain how to integrate using REST API.

Linkedin allows external partner websites and applications to use the LinkedIn API (Application Programming Interface). To understand Linkedin API and OAuth validation, visit https://developer.linkedin.com/docs/oauth2 and https://developer.linkedin.com/docs/rest-api

RequestFlow

To use LinkedIn API, first, you need to register your app on LinkedIn. To register your app, follow the following steps.

Step 1. Log into LinkedIn using your credentials for the Linkedin developer portal https://developer.linkedin.com.

Step 2. Go to "MyApps" section.

Step 3. Click on "Create Application".

linkedIN

Step 4. Enter all the necessary details related to your app.

Step 5. Click on the "Submit" button. On the next page, you will be provided with a "Client Id" and "Client Secret key". Save these values separately.

We will use these keys later in our web application to validate our request. On the page, check all the permission access options to get more information and add a redirect URL. This URL should be your app API which will capture the access code or error code returned from LinkedIn API as a parameter.

Here, our redirect URL is http://localhost:52986/Home/SaveLinkedinUser.

Step 6. Click "Update" and your app is ready to use.

Now, we will test this functionality using an ASP.NET MVC application (.NET Framework).

  1. Create an MVC project using VisualStudio IDE.
  2. Add Newtonsoft library from NuGet Package Manager into your project. This library will be used to serialize and deserialize JSON object.
  3. Add System.Net.Http assembly using "Add a reference" option. This will be used to request URLs using HttpClient class instance.
  4. Add a controller named Home and add the following code.

Note.

  • Refer fields returned by LinkedIn API here https://developer.linkedin.com/docs/fields/basic-profile
  • To know more about request parameter, follow https://developer.linkedin.com/docs/oauth2
using LogInUsingLinkedinApp.Models;
using LogInUsingLinkedinApp.Utils;
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace LogInUsingLinkedinApp.Controllers
{
    public class HomeController : Controller
    {
        private const string LinkedinUserInfoParameters = "id,first-name,last-name,maiden-name,formatted-name,phonetic-first-name,phonetic-last-name,formatted-phonetic-name,headline,location,picture-url,industry,current-share,num-connections,num-connections-capped,summary,specialties,positions,picture-urls,site-standard-profile-request,api-standard-profile-request,public-profile-url,email-address,languages,skills";

        public async Task<ActionResult> Index()
        {
            string token = (string)Session["user"];
            if (string.IsNullOrEmpty(token))
            {
                return View();
            }
            else
            {
                return View("User", await GetUserFromAccessTokenAsync(token));
            }
        }

        [HttpGet]
        public async Task<ActionResult> SaveLinkedinUser(string code, string state, string error, string error_description)
        {
            if (string.IsNullOrEmpty(code))
            {
                return View("Error");
            }

            var httpClient = new HttpClient
            {
                BaseAddress = new Uri("https://www.linkedin.com/")
            };

            var requestUrl = $"oauth/v2/accessToken?grant_type=authorization_code&code={code}&redirect_uri={AppConfig.Get("Linkedin.RedirectUrl")}&client_id={AppConfig.Get("Linkedin.ClientID")}&client_secret={AppConfig.Get("Linkedin.SecretKey")}";
            var response = await httpClient.GetAsync(requestUrl);
            var token = JsonConvert.DeserializeObject<TokenResponse>(await response.Content.ReadAsStringAsync());
            Session["user"] = token.Access_token;
            return View("User", await GetUserFromAccessTokenAsync(token.Access_token));
        }

        [HttpGet]
        public ActionResult SignOut()
        {
            Session["user"] = null;
            return View("Index");
        }

        private async Task<UserInfo> GetUserFromAccessTokenAsync(string token)
        {
            var apiClient = new HttpClient
            {
                BaseAddress = new Uri("https://api.linkedin.com")
            };

            var url = $"/v1/people/~:({LinkedinUserInfoParameters})?oauth2_access_token={token}&format=json";
            var response = await apiClient.GetAsync(url);
            var jsonResponse = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<UserInfo>(jsonResponse);
        }
    }
}

Step 7. Add a Models folder and add the following models. These models will be used to store the information received from LinkedIn API.

using Newtonsoft.Json;
using System.Collections.Generic;

namespace LogInUsingLinkedinApp.Models
{
    // JsonProperty(PropertyName = "name") attribute used here
    // to map JSON property names with object property names
    // while deserializing JSON into objects

    public class HeaderValue
    {
        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "value")]
        public string Value { get; set; }
    }

    public class Headers
    {
        [JsonProperty(PropertyName = "_total")]
        public int Total { get; set; }

        [JsonProperty(PropertyName = "values")]
        public List<HeaderValue> Values { get; set; }
    }

    public class ApiStandardProfileRequest
    {
        [JsonProperty(PropertyName = "headers")]
        public Headers Headers { get; set; }

        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }
    }

    public class Company
    {
        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int? Id { get; set; }

        [JsonProperty(PropertyName = "industry")]
        public string Industry { get; set; }

        [JsonProperty(PropertyName = "size")]
        public string Size { get; set; }

        [JsonProperty(PropertyName = "type")]
        public string Type { get; set; }
    }

    public class Country
    {
        [JsonProperty(PropertyName = "code")]
        public string Code { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }
    }

    public class Location
    {
        [JsonProperty(PropertyName = "country")]
        public Country Country { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }
    }

    public class StartDate
    {
        [JsonProperty(PropertyName = "month")]
        public int Month { get; set; }

        [JsonProperty(PropertyName = "year")]
        public int Year { get; set; }
    }

    public class Experience
    {
        [JsonProperty(PropertyName = "company")]
        public Company Company { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "isCurrent")]
        public bool IsCurrent { get; set; }

        [JsonProperty(PropertyName = "location")]
        public Location Location { get; set; }

        [JsonProperty(PropertyName = "startDate")]
        public StartDate StartDate { get; set; }

        [JsonProperty(PropertyName = "title")]
        public string Title { get; set; }
    }

    public class Positions
    {
        [JsonProperty(PropertyName = "_total")]
        public int Total { get; set; }

        [JsonProperty(PropertyName = "values")]
        public List<Experience> Values { get; set; }
    }

    public class SiteStandardProfileRequest
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }
    }

    public class UserInfo
    {
        [JsonProperty(PropertyName = "apiStandardProfileRequest")]
        public ApiStandardProfileRequest ApiStandardProfileRequest { get; set; }

        [JsonProperty(PropertyName = "emailAddress")]
        public string EmailAddress { get; set; }

        [JsonProperty(PropertyName = "firstName")]
        public string FirstName { get; set; }

        [JsonProperty(PropertyName = "formattedName")]
        public string FormattedName { get; set; }

        [JsonProperty(PropertyName = "headline")]
        public string Headline { get; set; }

        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }

        [JsonProperty(PropertyName = "industry")]
        public string Industry { get; set; }

        [JsonProperty(PropertyName = "lastName")]
        public string LastName { get; set; }

        [JsonProperty(PropertyName = "location")]
        public Location Location { get; set; }

        [JsonProperty(PropertyName = "maidenName")]
        public string MaidenName { get; set; }

        [JsonProperty(PropertyName = "numConnections")]
        public int NumConnections { get; set; }

        [JsonProperty(PropertyName = "numConnectionsCapped")]
        public bool NumConnectionsCapped { get; set; }

        [JsonProperty(PropertyName = "pictureUrl")]
        public string PictureUrl { get; set; }

        [JsonProperty(PropertyName = "positions")]
        public Positions Positions { get; set; }

        [JsonProperty(PropertyName = "publicProfileUrl")]
        public string PublicProfileUrl { get; set; }

        [JsonProperty(PropertyName = "siteStandardProfileRequest")]
        public SiteStandardProfileRequest SiteStandardProfileRequest { get; set; }

        [JsonProperty(PropertyName = "summary")]
        public string Summary { get; set; }
    }
}
using Newtonsoft.Json;

namespace LogInUsingLinkedinApp.Models
{
    public class TokenResponse
    {
        [JsonProperty(PropertyName = "access_token")]
        public string Access_token { get; set; }

        [JsonProperty(PropertyName = "expires_in")]
        public int Expires_in { get; set; }
    }
}

Step 8. Now, add Views to log in and display user profile.

File : Index.cshtml : For login using Linkedin page

@using LogInUsingLinkedinApp.Utils;

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Login</title>
</head>
<body style="background-color:#f3f3f3;">
    <div style="align-items:center;align-items:center;padding:50px;border:1px double #3d2487;width:260px;background-color:white">
        <h2>Hi Guest</h2>
        <p>
            <a href="https://www.linkedin.com/oauth/v2/authorization?response_type=code&[email protected]("Linkedin.ClientID")&[email protected]("Linkedin.RedirectUrl")&state=987654321&scope=r_emailaddress%20r_basicprofile">
                <img src="~/Resources/login-linkedin.png" style="width:215px;height:41px" />
            </a>
        </p>
    </div>
</body>
</html>

File : User.cshtml : To display user data

@model LogInUsingLinkedinApp.Models.UserInfo

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>User Profile</title>
</head>
<body style="border:0px;background-color:black;color:white">
    <table style="width:100%;border:0px">
        <tr>
            <td><img src="@Html.DisplayFor(model => model.PictureUrl)" /></td>
            <td>
                <i style="color:yellow;font-size:23px">@Html.DisplayFor(model => model.EmailAddress)</i>
                <a href="~/Home/SignOut" style="border:1px dashed yellow;border-radius:4px;padding:8px;color:orange;font-size:17px">SignOut</a>
                <p>
                    <b style="color:yellow">Summary : </b>@Html.DisplayFor(model => model.Summary)
                </p>
            </td>
        </tr>
        <tr>
            <td></td>
            <td>
                <div>
                    <p>
                        <b style="color:yellow"> First Name : </b>@Html.DisplayFor(model => model.FirstName)
                    </p>
                    <p>
                        <b style="color:yellow"> Maiden Name :</b> @Html.DisplayFor(model => model.MaidenName)
                    </p>
                    <p>
                        <b style="color:yellow"> Last Name : </b> @Html.DisplayFor(model => model.LastName)
                    </p>
                    <p>
                        <b style="color:yellow">Formatted Name :</b> @Html.DisplayFor(model => model.FormattedName)
                    </p>
                    <p>
                        <b style="color:yellow"> HeadLine : </b> @Html.DisplayFor(model => model.Headline)
                    </p>
                    <p>
                        <b style="color:yellow"> Public Profile Url : </b><a href="@Html.DisplayFor(model => model.PublicProfileUrl)">@Html.DisplayFor(model => model.PublicProfileUrl)</a>
                    </p>
                    <p>
                        <b style="color:yellow"> Industry :</b> @Html.DisplayFor(model => model.Industry)
                    </p>
                    <p>
                        <b style="color:yellow"> Connections : </b> @Html.DisplayFor(model => model.NumConnections)
                    </p>
                    <p>
                        <b style="color:yellow"> Connections capped :</b> @Html.DisplayFor(model => model.NumConnectionsCapped)
                    </p>
                </div>
            </td>
        </tr>
        @if (Model.Positions != null)
        {
            <tr>
                <td></td>
                <td>
                    <div>
                        <b style="color:yellow">Positions: </b>
                        <p>Total : @Model.Positions.Total</p>
                        @if (Model.Positions.Values != null)
                        {
                            foreach (var data in Model.Positions.Values)
                            {
                                <div style="padding:20px;border:1px dotted white;border-bottom:1px dotted white;">
                                    <p><b style="color:antiquewhite">Id :</b> @data.Id</p>
                                    <p><b style="color:antiquewhite">Company :</b> @data.Company.Name | Industry : @data.Company.Industry | Id : @data.Company.Id | Size :@data.Company.Size | Type: @data.Company.Type</p>
                                    <p><b style="color:antiquewhite">IsCurrent :</b>@data.IsCurrent</p>
                                    <p><b style="color:antiquewhite">Location :</b> @data.Location.Country.Code | @data.Location.Country.Name</p>
                                    <p><b style="color:antiquewhite">Start Date :</b> Month : @data.StartDate.Month | Year : @data.StartDate.Year</p>
                                    <p><b style="color:antiquewhite">Title :</b>@data.Title</p>
                                </div>
                            }
                        }
                    </div>
                </td>
            </tr>
        }

        <tr>
            <td></td>
            <td>
                @if (Model.SiteStandardProfileRequest != null)
                {
                    <p><b style="color:yellow"> SiteStandardProfileRequest :</b> <a href="@Model.SiteStandardProfileRequest.Url">@Model.SiteStandardProfileRequest.Url</a></p>
                }
                @if (Model.ApiStandardProfileRequest != null)
                {
                    <div>
                        <b style="color:yellow">ApiStandardProfileRequest :</b>
                        <p><b style="color:antiquewhite">url : </b><a href="@Model.ApiStandardProfileRequest.Url">@Model.SiteStandardProfileRequest.Url</a> </p>
                        <b style="color:antiquewhite">Header : </b>
                        @if (Model.ApiStandardProfileRequest.Headers != null)
                        {
                            <div style="padding:15px">
                                <b style="color:antiquewhite">Total :</b> @Model.ApiStandardProfileRequest.Headers.Total<br />
                                <b style="color:antiquewhite">Values:</b>
                                @foreach (var value in @Model.ApiStandardProfileRequest.Headers.Values)
                                {
                                    <p><i>Name :@value.Name , Value :@value.Value</i></p>
                                }
                            </div>
                        }
                    </div>
                }
            </td>
        </tr>
    </table>
</body>
</html>

File : Error.cshtml : To display error message

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Error</title>
</head>
<body>
    <h1>Ooops, Something went wrong</h1>
</body>
</html>

Step 9. Now, in Web.Config file, add the following configuration.

  1. <!--Configuration-->
  2. <add key="Linkedin.ClientID" value="81t4ctrlv5goix"/>
  3. <add key="Linkedin.SecretKey" value="5FSVMDbUIikls26z"/>
  4. <add key="Linkedin.RedirectUrl" value="http://localhost:52986/Home/SaveLinkedinUser"/>

Enter your own Client id and Secret key that you received while creating the app in Linkedin.

\Step 10. Run the Project and hit Url: http://localhost:52986/Home/Index and click on "Login using LinkedIn" button.

Now, the LinkedIn API will ask for your permission as shown in the figure to grant access to your app. Click on "Allow" button and you will see your basic details on our created page.

The user data response we will get from LinkedIn API will look like this.

{
    "apiStandardProfileRequest": {
        "headers": {
            "_total": 1,
            "values": [
                {
                    "name": "x-li-auth-token",
                    "value": "name:grK3"
                }
            ]
        },
        "url": "https://api.linkedin.com/v1/people/MtOEYw7xLz"
    },
    "emailAddress": "[email protected]",
    "firstName": "Tarun",
    "formattedName": "Tarun (Tarun Kumar Rajak) Rajak",
    "headline": "Senior Software Engineer at Cobb Systems Group, LLC",
    "id": "MtOEYw7xLz",
    "industry": "Information Technology and Services",
    "lastName": "Rajak",
    "location": {
        "country": {
            "code": "in"
        },
        "name": "Bengaluru Area, India"
    },
    "maidenName": "Tarun Kumar Rajak",
    "numConnections": 0,
    "numConnectionsCapped": false,
    "pictureUrl": "https://media.licdn.com/dms/image/C5103AQH7j-LxFD05IQ/profile-displayphoto-shrink_100_100/0?e=1528034400&v=alpha&t=yCO9gnj_yKKR_b3ZKB2Ds5WOKqiAnN5hMz6aZMa8dUg",
    "positions": {
        "_total": 2,
        "values": [
            {
                "company": {
                    "name": "Cobb Systems Group, LLC"
                },
                "id": 1258473040,
                "isCurrent": true,
                "location": {
                    "country": {
                        "code": "us",
                        "name": "United States"
                    },
                    "name": "United States"
                },
                "startDate": {
                    "month": 1,
                    "year": 2018
                },
                "title": "Senior Software Engineer"
            },
            {
                "company": {
                    "id": 1241076,
                    "industry": "Information Technology & Services",
                    "name": "CSG Solutions India Pvt. Ltd.",
                    "size": "2-10",
                    "type": "Privately Held"
                },
                "id": 1257788050,
                "isCurrent": true,
                "location": {
                    "country": {
                        "code": "in",
                        "name": "India"
                    },
                    "name": "Bengaluru Area, India"
                },
                "startDate": {
                    "month": 6,
                    "year": 2016
                },
                "title": "Software Engineer"
            }
        ]
    },
    "publicProfileUrl": "https://www.linkedin.com/in/tarun-rajak-41a332161",
    "siteStandardProfileRequest": {
        "url": "https://www.linkedin.com/profile/view?id=AAoAACardtYB8UA124fOjTz7p_hdHGN7ex7mnKI&authType=name&authToken=grK3&trk=api*a5219905*s5538355*"
    },
    "summary": "Tarun is a full stack Software Developer with a specialty in C#, SQL Server, ASP.NET MVC and other Microsoft technologies. He is expert in writing algorithms and currently working in CSG Solutions India Pvt Ltd."
}

Our code will deserialize this JSON data into an object named UserInfo.

User details page will look like the following one.

As the source code is larger than the upload limit, it is first compressed using the 7-Zip compressor and then it is zipped. Please first unzip and then decompress using the 7-Zip software.

For any queries and feedback, please write in the comment box. In coming articles, I will demonstrate how to integrate "Login using Gmail" and "Login using Facebook".


Recommended Free Ebook
Similar Articles