We are trying to use Microsoft Account Authentication in an ASP.Net 5 project. We don't require local authentication and don't require user names.
In the ASP.Net 5 Template for a web application, after signing in with an external provider, control returns to ExternalLoginCallback
in the AccountController.
If the user is not registered locally ExternalLoginCallback
returns the user to a registration screen. I have attempted to modify ExternalLoginCallback
to automatically register the new user as below.
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null)
{
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
{
_logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider);
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
//ViewData["ReturnUrl"] = returnUrl;
//ViewData["LoginProvider"] = info.LoginProvider;
//var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
//return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email });
// The user has not previously logged in with an external provider. Create a new user.
CreateUser(info);
return RedirectToLocal(returnUrl);
}
}
CreateUser implements code copied from ExternalLoginConfirmation
as it appears in the ASP.Net 5 Template for a web application.
private async void CreateUser(ExternalLoginInfo info)
{
var email = info.ExternalPrincipal.FindFirstValue(ClaimTypes.Email);
var user = new ApplicationUser { UserName = email, Email = email };
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
}
}
AddErrors(result);
}
An error is thrown on the line
var result = await _userManager.CreateAsync(user);
The error is
System.ObjectDisposedException was unhandled Message: An unhandled exception of type 'System.ObjectDisposedException' occurred in mscorlib.dll. Additional information: Cannot access a disposed object.
I have rebooted my machine just in case it was 'just one of those things', but the error recurs.
Using an async void
method is rarely a good idea and is the best way to introduce weird race conditions like the one you're experiencing: since your CreateUser
method doesn't return a task, it can't be awaited by ExternalLoginCallback
and the request completes before CreateUser
has the time to execute the database operations (when the request completes, the DI system call Dispose
on scoped dependencies like your EF context).
Update your CreateUser
method to return a Task
and await it from ExternalLoginCallback
and it should work:
await CreateUser(info);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With