This is the first post in a series on Abstracting External Login Providers with Autofac and the Factory Design Pattern

  • Part 1: Keeping it Local in the Action Method
  • Part 2: Factory to the Rescue
  • Part 3: Twitter Provider
  • Part 4: Autofac and Factory

Introduction

I’ve been implementing external login providers for the Humanitarian Toolbox’s AllReady humanitarian and disaster response open source project. I’ve already blogged about a mishap I had with User Secrets and a deep-dive on how to get email and name from Twitter’s external login provider.

In this blog post, I want to show the design and implementation changes I made to the surrounding codebase while implementing each of the external login providers. I’ll show you how I started with switch statements in a controller action method, slowly refactored to decompose, encapsulate and abstract the implementation of the different external login providers, and finally, how I wired it all up using the factory design pattern and Autofac.

And we’re off!

The First Attempt (Keeping it Local in the Action Method)

In AllReady, the external login work is handled by three action methods on the AccountController:

  • ExternalLogin
  • ExternalLoginCallback
  • ExternalLoginConfirmation

So, starting on the login screen:

LoginToAllReady

Clicking on any of the service logins will result in a request to the ExternalLogin action method.

This action method invokes SignInManager’s ExternalLoginSignInAsync method and returns a new ChallengeResult()

var redirectUrl = Url.Action(new UrlActionContext { Action = nameof(ExternalLoginCallback), Values = new { ReturnUrl = returnUrl }});  
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);  
return new ChallengeResult(provider, properties);  

The ExternalLogin action method then redirects to the ExternalLoginCallback action method, which depending on the external login provider you picked on the login screen, will attempt to populate four pieces of information that are required to register with the AllReady site.

  • First Name
  • Last Name
  • Email
  • Phone Number

RegisterWithGoogle

This page will attempt to fill in those fields depending on the external login provider that has been picked.

But there is a problem.

Each external login provider has different Claims it uses to try to retrieve those four pieces of information. For example, Microsoft and Facebook only provide the ability to get at the first and last name via a “Name” claim:

var name = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Name);  

In order to get the first name separated from the last name, I split the Name claim on a space and “hope” I get a first name and last name value makes sense:

var name = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Name);  
if (string.IsNullOrEmpty(name))  
{  
    return Task.FromResult(externalUserInformation);  
}

var array = name.Split(‘ ‘);  
if (array.Length < 2)  
{  
    return Task.FromResult(externalUserInformation);  
}

externalUserInformation.FirstName = array[0];  
externalUserInformation.LastName = array[1];

return Task.FromResult(externalUserInformation);  

Google populates both a GivenName and Surname claim, so grabbing this information to show on the UI is very easy (thanks for being awesome Google):

var externalUserInformation = new ExternalUserInformation  
{  
    Email = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Email),  
    FirstName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.GivenName),  
    LastName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Surname)  
};

return Task.FromResult(externalUserInformation);  

Twitter … well, Twitter doesn’t make it easy to get at ANY of this information. In order to get at the user’s email, first or last name, read my blog post on the implementation. I’ll provide a very brief code snippet here as there are some strange claims that need to be accessed:

var externalUserInformation = new ExternalUserInformation();

var userId = externalLoginInfo.Principal.FindFirstValue("urn:twitter:userid");  
var screenName = externalLoginInfo.Principal.FindFirstValue("urn:twitter:screenname");

//(then a whole bunch of code dealing with LinqToTwitter…)  

My initial code to pick the correct Claim(s) from which to pull the information for the Register UI in the ExternalLoginCallback action method ended up looking like this:

if (externalLoginInfo.LoginProvider == "Google")  
{  
    //access the claims to get email, first name and last name  
}

if (externalLoginInfo.LoginProvider == "Facebook" || externalLoginInfo.LoginProvider == "Microsoft")  
{  
    //access the claims to get email, first name and last name  
}

if (externalLoginInfo.LoginProvider == "Twitter")  
{  
    //access the claims to get email, first name and last name  
}  

I would need to add another “if” block for each external login provider we wanted to add in the future. Seeing how this violated SRPand OCP, I immediately started to think how I could abstract away the retrieval of the needed values for each different external login provider from from the ExternalLoginCallback action method.

A factory was the first thing that came to mind.