How to add LinkedIn login to your Asp.Net Core app?

If you are of human origin, then most likely you hate remembering all the passwords different websites are forcing you to remember.

(Yes, I use LastPass but still, it is a major PITA)

That is why we have OAuth providers which allow us to login using Facebook, Microsoft, Google and plenty of other social providers which we use anyhow.

As I said previously, on this blog post I treat all of my readers with respect so I will save you 20 minutes of your life by repeating to you basic stuff like what OAuth is and how you can add to your Asp.Net Core app Facebook login.
(In case you would need that: here it is)

Here I will speak about a problem I hit recently: the list of build in providers is limited and we can’t really use the popular .NET 4.6.2 NuGet packages so the question emerged…

If you are busy and just want to have AddLinkedIn() in your code and don’t care about how it works you can either get the source code from my GitHub repo or add a Nivatech.Framework.OAuthProviders Nuget package to your project.

If you want to learn how to make your own OAuth provider for Asp.Net Core just keep reading…

How to add LinkedIn OAuth provider to Asp.Net Core app?

Like many of the things in core  – making your own LinkedIn (or any other) OAuth provider it turns to be a quite an easy task done in 3 simple steps.

First of all, you need to find some data about OAuth endpoints the provider you want to use exposes. This is always an easy task which takes 10 minutes of Binging with Google as each one of the providers has that publicly documented.

In case of LinkedIn, this is documented on their OAuth documentation page so we will take from their 3 endpoint URLs we need and make out of it LinkedInDefaults class

public static class LinkedInDefaults
    public static readonly string DisplayName = "LinkedIn";
    public static readonly string AuthorizationEndpoint = "";
    public static readonly string TokenEndpoint = "";
    public static readonly string UserInformationEndpoint = ",formatted-name,email-address,picture-url)";
    public const string AuthenticationScheme = "LinkedIn";

The second step is to define OAuthOptions file for LinkedIn provider like this

public class LinkedInOptions : OAuthOptions
    public LinkedInOptions()
        this.CallbackPath = new PathString("/signin-linkedin");
        this.AuthorizationEndpoint = LinkedInDefaults.AuthorizationEndpoint;
        this.TokenEndpoint = LinkedInDefaults.TokenEndpoint;

        this.UserInformationEndpoint = LinkedInDefaults.UserInformationEndpoint;

        this.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id", ClaimValueTypes.String);
        this.ClaimActions.MapJsonKey(ClaimTypes.Name, "formattedName", ClaimValueTypes.String);
        this.ClaimActions.MapJsonKey(ClaimTypes.Email, "emailAddress", ClaimValueTypes.Email);
        this.ClaimActions.MapJsonKey("picture", "pictureUrl", ClaimValueTypes.String);

A few points about this code sample:

  • The callback path is in every provider defined as /signin-SCHEMA_NAME
  • I am defining explicitly the scopes which you can decide to skip and just rely on the company settings defined in the LinkedIn portal
  • The last few lines of code are defining mappings between properties LinkedIn provider is using and the claims you want to store them under.

How can you possibly know what properties certain OAuth provider uses?

Good question – let me show it in the LinkedInHandler file – a 3rd piece of our LinkedIn core OAuth provider

public class LinkedinHandler : OAuthHandler<LinkedInOptions>
    protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
        // Retrieve user info
        var request = new HttpRequestMessage(HttpMethod.Get, this.Options.UserInformationEndpoint);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
        request.Headers.Add("x-li-format", "json");

        var response = await this.Backchannel.SendAsync(request, this.Context.RequestAborted);

        var content = await response.Content.ReadAsStringAsync();
        var user = JObject.Parse(content);

        OAuthCreatingTicketContext context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, this.Context, this.Scheme, this.Options, this.Backchannel, tokens, user);
        await this.Events.CreatingTicket(context);
        return new AuthenticationTicket(context.Principal, context.Properties, this.Scheme.Name);

Do you see the line where we parse content into user object? That is the place to put your breakpoint and check what keys json content contains and then you can map them in options as mentioned previously.

So everything is in place and all we need is to create nice extension method where we map options, handler and authorization schema/provider name

public static class OAuthExtension
    public static AuthenticationBuilder AddLinkedin(this AuthenticationBuilder builder, Action<LinkedInOptions> linkedinOptions)
        return builder.AddOAuth<LinkedInOptions, LinkedinHandler>("LinkedIn", linkedinOptions);

And that’s it – from this moment you can use the LinkedIn provider as any other built-in OAuth provider

    .AddLinkedin(options =>
        options.ClientId = this.Configuration.GetValue<string>("LIN_CLIENT");
        options.ClientSecret = this.Configuration.GetValue<string>("LIN_SECRET");

Adding support for other OAuth provider is pretty much the same set of steps (I needed support for GitHub provider so I added it to my GitHub repo) so you should be able to create your own with an ease after reading this article.

Off course if you want to add your providers to the repo so more people can benefit out of it you are more then welcome