The ExternalLoginCallback method

It's now time to add the second action method--the one that will receive the HTTP redirection from the external provider with the authorization code and act accordingly:

[HttpGet("ExternalLoginCallback")]
public async Task<IActionResult> ExternalLoginCallback(
string returnUrl = null, string remoteError = null)
{
if (!String.IsNullOrEmpty(remoteError))
{
// TODO: handle external provider errors
throw new Exception(String.Format("External Provider error:
{0}", remoteError));
}

// Extract the login info obtained from the External Provider
var info = await SignInManager.GetExternalLoginInfoAsync();
if (info == null)
{
// if there's none, emit an error
throw new Exception("ERROR: No login info available.");
}

// Check if this user already registered himself with this external
provider before
var user = await UserManager.FindByLoginAsync(info.LoginProvider,
info.ProviderKey);
if (user == null)
{
// If we reach this point, it means that this user never tried
to logged in
// using this external provider. However, it could have used
other providers
// and /or have a local account.
// We can find out if that's the case by looking for his e-mail
address.

// Retrieve the 'emailaddress' claim
var emailKey =
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
var email = info.Principal.FindFirst(emailKey).Value;

// Lookup if there's an username with this e-mail address in
the Db
user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
// No user has been found: register a new user
// using the info retrieved from the provider
DateTime now = DateTime.Now;

// Create a unique username using the 'nameidentifier'
claim
var idKey = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
var username = String.Format("{0}{1}{2}",
info.LoginProvider,
info.Principal.FindFirst(idKey).Value,
Guid.NewGuid().ToString("N"));

user = new ApplicationUser()
{
SecurityStamp = Guid.NewGuid().ToString(),
UserName = username,
Email = email,
CreatedDate = now,
LastModifiedDate = now
};

// Add the user to the Db with a random password
await UserManager.CreateAsync(
user,
DataHelper.GenerateRandomPassword());

// Assign the user to the 'RegisteredUser' role.
await UserManager.AddToRoleAsync(user, "RegisteredUser");

// Remove Lockout and E-Mail confirmation
user.EmailConfirmed = true;
user.LockoutEnabled = false;

// Persist everything into the Db
await DbContext.SaveChangesAsync();
}
// Register this external provider to the user
var ir = await UserManager.AddLoginAsync(user, info);
if (ir.Succeeded)
{
// Persist everything into the Db
DbContext.SaveChanges();
}
else throw new Exception("Authentication error");
}

// create the refresh token
var rt = CreateRefreshToken("TestMakerFree", user.Id);

// add the new refresh token to the DB
DbContext.Tokens.Add(rt);
DbContext.SaveChanges();

// create & return the access token
var t = CreateAccessToken(user.Id, rt.Value);

// output a <SCRIPT> tag to call a JS function
// registered into the parent window global scope
return Content(
"<script type="text/javascript">" +
"window.opener.externalProviderLogin(" +
JsonConvert.SerializeObject(t, JsonSettings) +
");" +
"window.close();" +
"</script>",
"text/html"
);
}

This is where all the magic takes place, as we'll be checking for a number of things and take action accordingly. Although we did our best to fill the preceding code with a relevant amount of comments that should explain well what happens line after line, let's try to summarize everything:

  1. We check the external provider error message (if any) by looking at the remoteError parameter value. If something went bad, we throw an Exception here, otherwise, we go ahead.
  1. We extract the ExternalLoginInfo object using the SignInManager. This is a strongly-typed .NET object containing the response data sent by the external provider and decrypted by the Authentication.Facebook package of the ASP.NET Core Identity service. In the unlikely case that it happens to be null, we throw an Exception, otherwise, we go ahead.
  2. We check whether the user already authenticated himself with this external provider before using the UserManager.FindByLoginAsync method; if that's the case, we skip to step 8; otherwise, we need to do additional checks. It's the same approach we used within the Facebook() action method when we implemented our implicit flow.
  3. We need to check whether the user registered themselves before using different providers. To do so, we retrieve the user email from the ExternalLoginInfo object so that we can perform a database lookup to see whether we already have it. If that's the case, we skip to step 7; otherwise, we need to create it.
  4. We create a new user using the data we can retrieve from the relevant ExternalLoginInfo claims, including a temporary (yet unique) username and a random password that they'll be able to change in the future. We also assign them the registered user role.
  5. We associate the user with this external provider, so we'll be ready to handle further authentication attempts (skipping steps 5 to 7).
  6. We create our usual TokenResponseViewModel JSON object with the JWT auth and refresh tokens.
  7. However, instead of returning it like we always did before, we output a text/html response containing a <SCRIPT> tag with some JavaScript code that will be immediately executed by the user agent--arguably within the external provider pop-up window--to finalize the authentication cycle and close the popup.

The last step is very important and deserves some explanation. As we've already said more than once, the OAuth2 explicit flow is an interactive, redirection-driven process where the user has to manually interact with a consent form. In order to support that, we'll need to call these controller routes from a client-side pop-up window. That's why we need to call a function registered within the parent window--the window.opener--and also close the current one using window.close().

Wait a minute, are we really sending pure JavaScript code from within an API controller? Isn't that some kind of a quick-and-dirty hack to make everything look smooth instead of doing it in a proper way?

As a matter of fact, it definitely is! However, since there's no "proper way" of doing that in Angular, at least for the time being, we thought that it would still work for this quick implementation sample. Although there are some third-party packages in GitHub that can be used to provide Angular with some sort of native pop up/DOM pop up /modal pop up support, we restrained ourselves to integrate them here in an attempt to keep our code base as dry as possible. As always, the reader is highly encouraged to find a better and more robust solution than the one provided with our sample.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.78.102