Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failing to perform Cookie Authentication: SignInAsync and AuthenticateAsync not successful

I am trying to build a very simple playground server for me to study some ASP.NET Core authentication/authorization concepts. Basically a web app with a single, very simple controller, to be tested with Postman.

I came up with a minified version of my code, consisting of a single login endpoint which would authenticate the user (no credentials required) using Cookie Authentication, like that:

[ApiController]
public class MyController : ControllerBase
{
    [HttpGet("/login")]
    public async Task<IActionResult> Login()
    {
        var claims = new[] { new Claim("name", "bob") };
        var identity = new ClaimsIdentity(claims);
        var principal = new ClaimsPrincipal(identity);

        await HttpContext.SignInAsync(principal);
        return Ok();
    }
}

The thing is that the call to HttpContext.SignInAsync() is firing the following exception:

System.InvalidOperationException: SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.
   at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
   at MyController.Login() in C:\Users\vinic\Desktop\TEMP\TestesAuthorization\Controllers\MyController.cs:line 18

Then I tried to replace HttpContext.SignInAsync() by a call to HttpContext.AuthenticateAsync(), so that I could authenticate the user before trying to call SignInAsync() again:

[HttpGet("/login")]
public async Task<IActionResult> Login()
{
    var authResult = await HttpContext.AuthenticateAsync();
    if (authResult.Succeeded == false)
        return StatusCode(500, "Failed to autenticate!");
    return Ok();
}

But in that case the AuthenticateAsync() result always returns a failure (authResult.Succeeded = false), and later calls to HttpContext.SignInAsync() would fail with the same InvalidOperationException as before. By enabling "Trace"-level logging, the call to AuthenticateAsync() only logs the following (not very helpful) piece of information:

dbug: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[9]
      AuthenticationScheme: Cookies was not authenticated.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Debug: AuthenticationScheme: Cookies was not authenticated.

My project targets the net5.0 framework, has no external/explicit dependencies, and here's the Startup class I'm using:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    }


    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfiguration configs)
    {
        app.UseRouting();
        app.UseAuthentication();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

I know I must be missing something really basic here. I'm also not sure if the documentation I am basing myself on is actually up-to-date for .NET 5.0.

Why is the cookie authentication (HttpContext.SignInAsync() / HttpContext.AuthenticateAsync()) failing?

like image 803
vinicius.ras Avatar asked Jan 12 '21 18:01

vinicius.ras


People also ask

How to implement cookie authentication in ASP.NET Core?

Let's implement the Cookie Authentication in ASP.NET Core step by step. Open the Visual Studio and click on Create a new Project. Select ASP.NET Core Empty project and click on next. Give a name to your Project, select the location for the project creation, and click on Next.

What is CookieAuthenticationDefaults AuthenticationScheme?

AuthenticationScheme passed to AddAuthentication sets the default authentication scheme for the app. AuthenticationScheme is useful when there are multiple instances of cookie authentication and the app needs to authorize with a specific scheme. Setting the AuthenticationScheme to CookieAuthenticationDefaults.

What does HttpContext SignInAsync do?

SignInAsync(HttpContext, ClaimsPrincipal) Sign in a principal for the default authentication scheme. The default scheme for signing in can be configured using DefaultSignInScheme.


2 Answers

This was a breaking change since Asp.Net Core 3.0 Preview 6. The documentation is here https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnetcore#identity-signinasync-throws-exception-for-unauthenticated-identity, but it does not contain the motivation of the breaking change.

The real motivation is here: https://github.com/dotnet/aspnetcore/issues/9255

In short, you need to specify auth scheme explicitly:

new ClaimsIdentity(claims, /*Explicit*/CookieAuthenticationDefaults.AuthenticationScheme)

I had the same issue, and this change helped in my case.

like image 98
Dmitriy Ivanov Avatar answered Oct 11 '22 05:10

Dmitriy Ivanov


Just to build on Dmitriy's answer, here is a snippet of a working login (.NET 5.0, probably works for 3.0 and above):

var claims = new List<Claim>
{
    // example claims from external API
    new Claim("sub", userId), 
    new Claim(ClaimTypes.Name, username)
};

var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);
var signIn = HttpContext.SignInAsync(new ClaimsPrincipal(claimsIdentity),
                _userService.AuthenticationOptions(model.RememberMe));
like image 35
barnacle.m Avatar answered Oct 11 '22 07:10

barnacle.m