.netasp.net-mvcauthenticationasp.net-identityclaims-based-identity

.Net 8: Cannot authenticate user with "Individual Accounts Auth" template


I created a web app from scratch using the default .Net template for MVC (ASP.NET Core web app) using Auth with "Individual Accounts" option and SDK 8, and my problem is I'm not able to authenticate a user, at least SignInManager.IsSignedIn(User) always returns false.

Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    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.UseAuthorization();
app.UseAuthentication();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

Login.cshtml.cs:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);

        var result = SignInResult.Failed;
        AWS_Manager aws = new AWS_Manager(TestController.access_id, TestController.secret_key);

        UserModel oUser = await Task.Run(() => aws.getUser(Input.Email));

        if (oUser != null)
        {
            if (oUser.Password == Crypto.GetSHA1(Input.Password)))
            {
                SetUser(Input.Email);
                result = SignInResult.Success;
            }
        }

        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }

        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
        }

        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

SetUser:

private void SetUser(string email)
{
    var claims = new ClaimsIdentity(
        new[] { 
            // adding following 2 claim just for supporting default antiforgery provider
            new Claim(ClaimTypes.NameIdentifier, email),
            new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
            "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
            new Claim(ClaimTypes.Name, email),

            // optionally you could add roles if any
            // new Claim(ClaimTypes.Role, "RoleName"),
            // new Claim(ClaimTypes.Role, "AnotherRole"),

        }, IdentityConstants.ApplicationScheme);

    var claim = new ClaimsPrincipal(claims);
    HttpContext.SignInAsync(claim);

    //This next line is for testing and returns true
    var isLoggenIn = claim.Identities != null && claim.Identities.Any(i => i.AuthenticationType == IdentityConstants.ApplicationScheme); //=> Returns true
}

_LoginPartial.cshtml:

@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager

<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User)) //=> Always returns false
{
    <li class="nav-item">
        <a  class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity?.Name!</a>
    </li>
    <li class="nav-item">
        <form  class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new { area = "" })">
            <button  type="submit" class="nav-link btn btn-link text-dark">Logout</button>
        </form>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
    </li>
}
</ul>

As per all the threads / posts I've already read I believe my problem is in SetUser method, and most precisely in setting the AuthenticationType in the claims, but not completely sure, and I don't know how to do it.

What can I try next?

Edit 1

I'm quite sure I should add the AuthenticationType at the end of the

ClaimsIdentity constructor, like this (for example):
            var claims = new ClaimsIdentity(
                new[] { 
                    // adding following 2 claim just for supporting default antiforgery provider
                    new Claim(ClaimTypes.NameIdentifier, email),
                    new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
                    "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
                    new Claim(ClaimTypes.Name, email),

                    // optionally you could add roles if any
                    // new Claim(ClaimTypes.Role, "RoleName"),
                    // new Claim(ClaimTypes.Role, "AnotherRole"),

                }, "UserAuthenticated");

But then I don't know where / how to specify the "UserAuthenticated" AuthenticationType.


Solution

  • I'll answer myself and paste the relevant parts of code.

    Program.cs:

    builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {
            options.AccessDeniedPath = "/login";
            options.LoginPath = "/";
        });
    

    Login.cshtml.cs

    AWS_Manager aws = new AWS_Manager(TestController.access_id, TestController.secret_key);
    
    UserModel oUser = await Task.Run(() => aws.getUser(Input.Email));
    if (oUser != null)
    {
        if (oUser.Password == Crypto.GetSHA1(Input.Password)) // && oUser.testloader_enabled == true)
        {
            //SetUser(Input.Email);
            //result = SignInResult.Success;
    
            //A claim is a statement about a subject by an issuer and    
            //represent attributes of the subject that are useful in the context of authentication and authorization operations.    
            var claims = new List<Claim>() {
                        // adding following 2 claim just for supporting default antiforgery provider
                        new Claim(ClaimTypes.NameIdentifier, Input.Email),
                        new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
                        "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
                        new Claim(ClaimTypes.Name, Input.Email),
    
                        // optionally you could add roles if any
                        // new Claim(ClaimTypes.Role, "RoleName"),
                        // new Claim(ClaimTypes.Role, "AnotherRole"),
            };
            //Initialize a new instance of the ClaimsIdentity with the claims and authentication scheme    
            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            //Initialize a new instance of the ClaimsPrincipal with ClaimsIdentity    
            var principal = new ClaimsPrincipal(identity);
            //SignInAsync is a Extension method for Sign in a principal for the specified scheme.    
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
            {
                IsPersistent = Input.RememberMe
            });
            return LocalRedirect(returnUrl);
        }
    }
    

    _LoginPartial.cshtml:

    @if (User.Identity != null && User.Identity.IsAuthenticated)
    {
        ...
    

    Now it's working.