I have an ASP.NET Core 2.x project with the following configuration:
services
.AddAuthentication(options => options.DefaultScheme = CookieAuthenticaitonDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddFacebook(ConfigureFacebook);
Predictably, when I call from one of my actions:
return Challenge(new AuthenticationProperties { RedirectUri = "/test" }, "Facebook");
... Then, I get navigated through the Facebook OAuth sequence. When I find my way back to my app, HttpContext.User.Identity
is populated with the relevant details:
User.Identity.Name
- The Facebook user name.User.Identity.AuthenticationType
- The string "Facebook"
.User.Identity.IsAuthenticated
- true
.This is all well and as is expected. However, if I add to my application configuration the following
services.AddIdentity<MyUserType, MyRoleType>()
.AddEntityFrameworkStores<MyDbContext>();
Suddenly, the OAuth flow ends in User.Identity
being anonymous without anything else changing. If we drill into IdentityServiceCollectionExtensions.cs, we find:
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
among other things...
What is going on here? Why is Identity interfering with the Cookie process, and what is the correct way to get the User returned from an OAuth provider?
When combining ASP.NET Identity and OAuth, certain considerations need to be made:
Adding AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
is no longer necessary because Identity adds its own cookie handlers.
If you want the external user to be populated under HttpContext.User
, do the following:
.AddFacebook(options => {
options.SignInScheme = IdentityConstants.ApplicationScheme;
})
After being redirected to the RedirectUri
in your challenge's AuthenticationProperties
, your HttpContext.User
will be populated.
ExternalLoginInfo
:This is preferred if you need to know things about the user such as:
Your services should be configured like so:
services.AddAuthentication()
.AddFacebook(options =>
{
options.AppId = "";
options.AppSecret = "";
});
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<MyDbContext>();
In your login controller, inject SignInManager<TUser>
in:
public DefaultController(SIgnInManager<IdentityUser> signInManager)
And in your challenge action, use ConfigureExternalAuthenticationProperties
to get the challenge properties:
public IActionResult LoginExternal() {
var props = SignInManager.ConfigureExternalAuthenticationProperties("Facebook", "/");
return Challenge(props, "Facebook");
}
In your return action, use GetExternalLoginInfoAsync
to get the external details about the user:
public async Task<IActionResult> LoginCallback() {
var loginInfo = await SignInManager.GetExternalLoginInfoAsync();
// This object will tell you everything you need to know about the incoming user.
}