ASP.NET Core: Abstracting External Login Providers with Autofac and the Factory Design Pattern: Part 3

ASP.NET Core: Abstracting External Login Providers with Autofac and the Factory Design Pattern: Part 3

This is the third 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

Singleton Scoping

At this point in the refactoring, I was still in the process of figuring out/finalizing the Twitter provider. I won’t show you the whole provider here (you can check it out directly on AllReady’s GitHub), but the provider needed four pieces of information that were being kept in the config.json file.

  • ConsumerKey
  • ConsumerSecret
  • OAuthToken
  • OAuthSecret

I needed to get a hold of these values because the Twitter provider used LinqToTwitter to make an additional HTTP call after the initial authentication request had succeeded to get the Name and Email claim. The code looked like this:

public class TwitterExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    public async Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
    …  
        var authTwitter = new SingleUserAuthorizer  
        {  
            CredentialStore = new SingleUserInMemoryCredentialStore  
            {  
                ConsumerKey = //need this value from config.json file  
                ConsumerSecret = //need this value from config.json file,  
                OAuthToken = //need this value from config.json file,  
                OAuthTokenSecret = //need this value from config.json file,  
            }  
        };

        await authTwitter.AuthorizeAsync();  
        var twitterCtx = new TwitterContext(authTwitter);  
        …  
    }  
}  

For those of you new to ASP.NET Core, you are no longer able to access configuration values statically (more information about this, see my post ASP.NETCore: User Secrets, Secret Manager and External Logins, oh my!).

To get at configuration values in your code, you need to inject IOptions into the class that needs the configuration values where T is a strongly typed configuration class that “wraps” the values in config.json (for more information on strongly typed configuration classes in ASP.NET Core, please see this post).

I created a strongly typed configuration class to wrap the values in config.json for the Twitter provider.

public class TwitterAuthenticationSettings  
{  
    public string ConsumerKey { get; set; }  
    public string ConsumerSecret { get; set; }  
    public string OAuthToken { get; set; }  
    public string OAuthSecret { get; set; }  
}  

and injected IOptions into TwitterExternalInformationProvider so I could pull those values from configuration:

public TwitterExternalUserInformationProvider(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  

But now I had a problem.

Since my factory was instantiating TwitterExternalUserInformationProvider, I needed a way to provide an instance of IOptions to it. The simplest thing that worked was injecting IOptions into the factory class’s constructor. So my factory class now looked like this:

public class ExternalUserInformationProviderFactory : IExternalUserInformationProviderFactory  
{  
    private readonly IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings;

    public ExternalUserInformationProviderFactory(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  
    {  
        this.twitterAuthenticationSettings = twitterAuthenticationSettings;  
    }

    public IProvideExternalUserInformation GetExternalUserInformationProviderFor(string loginProvider)  
    {  
        switch (loginProvider)  
        {  
            case "Facebook":  
                return new FacebookExternalUserInformationProvider();  
            case "Google":  
                return new GoogleExternalUserInformationProvider();  
            case "Microsoft":  
                return new MicosoftExternalUserInformationProvider();  
            case "Twitter":  
                return new TwitterExternalUserInformationProvider(twitterAuthenticationSettings);  
       }  
    return null;
    }  
}  

Since IOptions are scoped as singletons by ASP.NET Core’s built-in dependency injection, I added my strongly typed configuration class in Startup.cs:

services.Configure<TwitterAuthenticationSettings>(Configuration.GetSection("Authentication:Twitter"));  

I then ran the code and the injection worked.

But it bothered me that I was passing in a dependency to the factory class that was only being utilized by one of the providers. That seemed to be a bit of a “wonky” workaround, but the code I had at this time worked.

This solution ended up breaking down when through further refactoring, I had to add another dependency to TwitterExternalUserInformationProvider. But this time, I needed to scope the dependency as a transient.

Transient Scoping

In TwitterExternalUserInformationProvider, I was doing some work with claims:

…
var externalUserInformation = new ExternalUserInformation();  
var userId = externalLoginInfo.ExternalPrincipal.FindFirstValue("urn:twitter:userid");  
var screenName = externalLoginInfo.ExternalPrincipal.FindFirstValue("urn:twitter:screenname");  
…  

invoking the LinqToTwitter API directly:

var authTwitter = new SingleUserAuthorizer  
{  
    CredentialStore = new SingleUserInMemoryCredentialStore  
    {  
        ConsumerKey = twitterAuthenticationSettings.Value.ConsumerKey,  
        ConsumerSecret = twitterAuthenticationSettings.Value.ConsumerSecret,  
        OAuthToken = twitterAuthenticationSettings.Value.OAuthToken,  
        OAuthTokenSecret = twitterAuthenticationSettings.Value.OAuthSecret, 
            UserID = ulong.Parse(userId), ScreenName = screenName  
    }  
};

await authTwitter.AuthorizeAsync();

var twitterCtx = new TwitterContext(authTwitter);

var verifyResponse = await (from acct in twitterCtx.Account 
    where (acct.Type == AccountType.VerifyCredentials) 
    && (acct.IncludeEmail == true) 
    select acct).SingleOrDefaultAsync();  

and finally using the data returned via LinqToTwitter to populate ExternalUserInformation:

…
if (verifyResponse != null && verifyResponse.User != null)  
{  
    var twitterUser = verifyResponse.User;  
    if (twitterUser != null)  
    {  
        externalUserInformation.Email = twitterUser.Email;

        if (!string.IsNullOrEmpty(twitterUser.Name))  
        {  
            var array = twitterUser.Name.Split(‘ ‘);  
            if (array.Length > 1)  
            {  
                externalUserInformation.FirstName = array[0];  
                externalUserInformation.LastName = array[1];  
            }  
       }  
     }  
}

return externalUserInformation;  

In short, TwitterExternalUserInformationProvider was doing a lot of work.

Upon closer inspection, I saw this class was violating SRP. I really felt the friction with the class when I went to write a unit test and found I could not write it because of the hard dependency on LinqToTwitter.

As with most things in software development, adding another layer of abstraction helped me remove the LinqToTwitter dependency out of TwitterExternalUserInformationProvider. I did this by creating an abstraction for LinqToTwitter and moving the implementation to a separate class.

Here is the abstraction:

public interface ITwitterRepository  
{  
    Task<Account> GetTwitterAccount(string userId, string screenName);  
}  

and here is the implementation:

public class TwitterRepository : ITwitterRepository  
{  
    private readonly IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings;

    public TwitterRepository(IOptions<TwitterAuthenticationSettings> twitterAuthenticationSettings)  
    {  
        this.twitterAuthenticationSettings = twitterAuthenticationSettings;  
    }

    public async Task<Account> GetTwitterAccount(string userId, string screenName)  
    {  
        var authTwitter = new SingleUserAuthorizer  
        {  
            CredentialStore = new SingleUserInMemoryCredentialStore  
            {  
                ConsumerKey = twitterAuthenticationSettings.Value.ConsumerKey,  
                ConsumerSecret = twitterAuthenticationSettings.Value.ConsumerSecret,  
                OAuthToken = twitterAuthenticationSettings.Value.OAuthToken,  
                OAuthTokenSecret = twitterAuthenticationSettings.Value.OAuthSecret,  
                UserID = ulong.Parse(userId),  
                ScreenName = screenName  
            }  
        };

        await authTwitter.AuthorizeAsync();

        var twitterCtx = new TwitterContext(authTwitter);

        var account = await (from acct in twitterCtx.Account 
            where (acct.Type == AccountType.VerifyCredentials) 
            && (acct.IncludeEmail == true)  
            select acct).SingleOrDefaultAsync();

        return account;  
     }  
}  

Now the TwitterExternalUserInformationProvider class delegated the call to LinqToTwitter via the injected ITwitterRepository :

public class TwitterExternalUserInformationProvider : IProvideExternalUserInformation  
{  
    private readonly ITwitterRepository twitterRepository;

    public TwitterExternalUserInformationProvider(ITwitterRepository twitterRepository)  
    {  
        this.twitterRepository = twitterRepository;  
    }

    public async Task<ExternalUserInformation> GetExternalUserInformation(ExternalLoginInfo externalLoginInfo)  
    {  
        var externalUserInformation = new ExternalUserInformation();  
        var userId = externalLoginInfo.Principal.FindFirstValue("urn:twitter:userid");  
        var screenName = externalLoginInfo.Principal.FindFirstValue("urn:twitter:screenname");

        var twitterAccount = await twitterRepository.GetTwitterAccount(userId, screenName);

        if (twitterAccount != null && twitterAccount.User != null)  
        {  
            var twitterUser = twitterAccount.User;  
            externalUserInformation.Email = twitterUser.Email;

            if (!string.IsNullOrEmpty(twitterUser.Name))  
            {  
                var array = twitterUser.Name.Split(‘ ‘);  
                if (array.Length > 1)  
                {  
                    externalUserInformation.FirstName = array[0];  
                    externalUserInformation.LastName = array[1];  
                }  
            }  
        }  
        return externalUserInformation;  
    }  
}  

Much better! I was no longer violating SRP.

But now I faced the challenge of how to get a transient instance of ITwitterRepository injected into TwitterExternalUserInformationProvider. I initially set out to inject it via ExternalUserInformationProviderFactory (like I did for IOptions) and then pass it to the instantiation of the twitter provider, but the ITwitterRepository needed to be scoped as transient and my factory was scoped as a singleton.

How was I going to provide a transient scoped dependency to a class that itself, was also being instantiated by the container, but scoped as singleton?

The encapsulation of the LinqToTwitter code in TwitterRepository was the right thing to do, but I needed a mechanism that could resolve the correct type at run-time that did NOT rely on a singleton scoped factory.

Enter Autofac’s IComponentContext…

Show Comments