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
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
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
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
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
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…