External Login

Danijel Bokšan 6 Reputation points
2022-04-13T10:43:52.777+00:00

Dear,

I have problem with external login, I had follow tutorial from Microsoft Learn, but for some reason when I need to post ExternalLoginConfirmation , Principal are null.
But I had pass the login on the . google

Here is my code.
Program.cs
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Identity;
using Milking.Infrastructure;

var builder = WebApplication.CreateBuilder(args);  
  
builder.Services.AddInfrastructure(builder.Configuration);  
// Add services to the container.  
builder.Services.AddControllersWithViews();  
  
  
  
builder.Services.AddAuthentication()  
    .AddGoogle(options =>  
    {  
        IConfigurationSection googleAuthSection = builder.Configuration.GetSection("Authentication:Google");  
        options.ClientId = googleAuthSection["ClientId"];  
        options.ClientSecret = googleAuthSection["ClientSecret"];  
  
    })  
    .AddMicrosoftAccount(options =>  
    {  
        IConfigurationSection microsoftAuthSection = builder.Configuration.GetSection("Authentication:Microsoft");  
        options.ClientId = microsoftAuthSection["ClientId"];  
        options.ClientSecret = microsoftAuthSection["ClientSecret"];  
  
    });  
  
var app = builder.Build();  
  
// Configure the HTTP request pipeline.  
if (!app.Environment.IsDevelopment())  
{  
    app.UseExceptionHandler("/Home/Error");  
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.  
    app.UseHsts();  
}  
  
  
app.UseHttpsRedirection();  
app.UseStaticFiles();  
  
app.UseRouting();  
  
app.UseAuthentication();  
app.UseAuthorization();  
  
app.MapControllerRoute(  
    name: "default",  
    pattern: "{controller=Home}/{action=Index}/{id?}");  
  
app.Run();  

AuthController.cs

using System.Security.Claims;  
using Microsoft.AspNetCore.Identity;  
using Microsoft.AspNetCore.Mvc;  
using Milking.WebMVC.Models;  
  
namespace Milking.WebMVC.Controllers;  
  
public class AuthController : Controller  
{  
    private readonly SignInManager<IdentityUser> _signInManager;  
    private readonly UserManager<IdentityUser> _userManager;  
  
    public AuthController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)  
    {  
        _signInManager = signInManager;  
        _userManager = userManager;  
    }  
  
    [HttpGet]  
    public IActionResult Login()  
    {  
        return View();  
    }  
  
    [HttpPost]  
    [ValidateAntiForgeryToken]  
    public IActionResult ExternalLogin(string provider)  
    {  
        var redirectUrl = Url.Action(nameof(ExternalLoginCallback), "Auth");  
        var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);  
        return Challenge(properties, provider);  
    }  
  
    [HttpGet]  
    public async Task<IActionResult> ExternalLoginCallback()  
    {  
        var info = await _signInManager.GetExternalLoginInfoAsync();  
        if (info is null)  
        {  
            //return RedirectToAction(nameof(Login));  
            return BadRequest();  
        }  
        var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: false);  
        if (signInResult.Succeeded)  
        {  
            return RedirectToAction(nameof(Login));  
        }  
  
        ViewData["Provider"] = info.LoginProvider;  
        var email = info.Principal.FindFirstValue(ClaimTypes.Email);  
        return View("ExternalLogin", new ExternalLoginModel { Email = email });  
    }  
  
    [HttpPost]  
    [ValidateAntiForgeryToken]  
    public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginModel model)  
    {  
        if (!ModelState.IsValid)  
            return View(model);  
  
        var info = await _signInManager.GetExternalLoginInfoAsync();  
        if (info == null)  
            return Content("system has error");  
  
        var user = await _userManager.FindByEmailAsync(model.Email);  
        IdentityResult result = new();  
  
        if (user != null)  
        {  
            var logins = await _userManager.GetLoginsAsync(user);  
            if (!logins.Any())  
            {  
                result = await _userManager.AddLoginAsync(user, info);  
                if (!result.Succeeded)  
                {  
                    ModelState.TryAddModelError(string.Empty, result.Errors.Select(_ => _.Description).FirstOrDefault());  
                    return View(nameof(ExternalLogin), model);  
                }  
            }  
  
            await _signInManager.SignInAsync(user, isPersistent: false);  
            return RedirectToAction("Index", "Home");  
        }  
        else  
        {  
            model.Principal = info.Principal;  
  
            user = new(model.Email)  
            {  
                Email = model.Email  
            };  
  
            result = await _userManager.CreateAsync(user);  
            if (result.Succeeded)  
            {  
                result = await _userManager.AddLoginAsync(user, info);  
                if (result.Succeeded)  
                {  
                    //TODO: Send an email for the email confirmation and add a default role as in the Register action  
                    await _signInManager.SignInAsync(user, isPersistent: false);  
                    return RedirectToAction("Index", "Home");  
                }  
            }  
        }  
  
        foreach (var error in result.Errors)  
        {  
            ModelState.TryAddModelError(error.Code, error.Description);  
        }  
  
        return View(nameof(ExternalLogin), model);  
    }  
  
}  

And from another layer which is calling AddInfrastructure

using Microsoft.AspNetCore.Identity;  
using Microsoft.EntityFrameworkCore;  
using Microsoft.Extensions.Configuration;  
using Microsoft.Extensions.DependencyInjection;  
using Milking.Infrastructure.Data;  
  
namespace Milking.Infrastructure;  
  
public static class DependencyInjection  
{  
    public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)  
    {  
  
        services.AddDbContext<IdentityAppContext>(options =>  
        {  
            options.UseSqlServer(configuration.GetConnectionString("IdentityConnection"));  
        });  
  
        services.AddIdentity<IdentityUser, IdentityRole>(options =>  
            {  
                options.SignIn.RequireConfirmedEmail = true;  
            })  
            .AddEntityFrameworkStores<IdentityAppContext>()  
            .AddDefaultTokenProviders()  
            .AddErrorDescriber<IdentityErrorDescriber>();  
  
        return services;  
    }  
}  

Error picture:
192731-error.png

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,190 questions
{count} vote

1 answer

Sort by: Most helpful
  1. Zhi Lv - MSFT 32,016 Reputation points Microsoft Vendor
    2022-04-14T07:24:20.997+00:00

    Hi @Danijel Bokšan ,

    From examples https://code-maze.com/external-identity-provider-aspnet-core-identity/ they have it like I, and their example is working and my not ...
    Another think if I populate in ExternalLoginCallback even Principal when come to ExternalLoginConfirmation it is lost. I temporary solution I had found that I create one private field for Principal and populate that one and later in ExternalLoginConfirmation if is null I'm giving him that value. But for me this is not okay and it should be temporary...

    The tutorial and the official document are using the default Identity's ExternalLogin page. You could refer the following steps to add the Identity ExternalLogin page:

    Right click the project, click Add => New Scaffolded Item...=> Identity => Add

    192999-image.png

    Then, in the popup window, select the ExternalLogin and select your dbcontext, then click the Add button, like this:

    192967-image.png

    After that, it will add the Identity page like this:

    192938-image.png

    Then, you can use the ExternalLogin and refer the following document to achieve the External Login function.

    Microsoft Account external login setup with ASP.NET Core

    Google external login setup in ASP.NET Core

    View or download official sample code


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,
    Dillion

    0 comments No comments