Home > Software design >  ASP.NET 6 Restful Api Cannot Authenticate with cookies
ASP.NET 6 Restful Api Cannot Authenticate with cookies

Time:06-18

I am developing an ASP.NET 6 Restful API as a homework for collage. I would like to introduce AAA into my API. I have many allowed options and I've chosen Core Identity cookies. I followed as many tutorials as I could, but to no avail.

I don't have any concrete client (and my task does not include one), I am using Postman and Telerik Fiddler to send requests.

My main problem is: after I log in using my endpoint i still cannot authenticate the user and access non-anonymous endpoints. If I try to use them I get a redirect:

[15:35:24 INF] Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
[15:35:24 INF] AuthenticationScheme: Identity.Application was challenged.
[15:35:24 INF] HTTP GET /api/User/getDemoAuthorized responded 302 in 48.2047 ms
[15:35:24 INF] Request finished HTTP/2 GET https://localhost:7166/api/User/getDemoAuthorized - - - 302 0 - 98.8841ms
[15:35:24 INF] Request starting HTTP/2 GET https://localhost:7166/Identity/Account/Login?ReturnUrl=/api/User/getDemoAuthorized - -
[15:35:24 DBG] No candidates found for the request path '/Identity/Account/Login'
[15:35:24 DBG] Request did not match any endpoints
[15:35:24 DBG] The request path  does not match the path filter
[15:35:24 DBG] The request path /Identity/Account/Login does not match a supported file type
[15:35:24 DBG] AuthenticationScheme: Identity.Application was not authenticated.
[15:35:24 DBG] List of registered output formatters, in the following order: ["Microsoft.AspNetCore.Mvc.Formatters.HttpNoContentOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.StringOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.StreamOutputFormatter", "Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter"]
[15:35:24 DBG] No information found on request to perform content negotiation.
[15:35:24 DBG] Attempting to select the first output formatter in the output formatters list which supports a content type from the explicitly specified content types '["application/problem json", "application/problem xml", "application/problem json", "application/problem xml"]'.
[15:35:24 DBG] Selected output formatter 'Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter' and content type 'application/problem json' to write the response.
[15:35:24 INF] Executing ObjectResult, writing value of type 'Microsoft.AspNetCore.Mvc.ProblemDetails'.
[15:35:24 INF] HTTP GET /Identity/Account/Login responded 404 in 36.7039 ms

Note: I cannot use Razor webpages, just a simple restful API

When using an anonymous end-point I can see that my user is not authenticated: user not authenticaed picture

Log of when I try to log in:

[15:53:37 DBG] Context 'AppDbContext' started tracking 'IdentityUser' entity. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see key values.
[15:53:37 DBG] A data reader was disposed.
[15:53:37 DBG] Closing connection to database 'caloryDb' on server '(localdb)\MSSQLLocalDB'.
[15:53:37 DBG] Closed connection to database 'caloryDb' on server '(localdb)\MSSQLLocalDB'.
[15:53:40 INF] AuthenticationScheme: caloryCookieAuth signed in.
[15:53:40 INF] Executing StatusCodeResult, setting HTTP status code 204
[15:53:40 INF] Executed action CaloryWebApi.Api.Controllers.UserController.LoginAsync (CaloryWebApi.Api) in 7301.0998ms
[15:53:40 INF] Executed endpoint 'CaloryWebApi.Api.Controllers.UserController.LoginAsync (CaloryWebApi.Api)'
[15:53:40 INF] HTTP POST /api/User/login responded 204 in 7407.4312 ms

User controller:

[Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class UserController : ControllerBase
    {

        private readonly IUserService _userService;
        private readonly SignInManager<IdentityUser> _signInManager;

        public UserController(IUserService userService, SignInManager<IdentityUser> signInManager)
        {
            _userService = userService;
            _signInManager = signInManager;
        }

        [HttpGet("getDemoAnonymous")]
        [AllowAnonymous]
        public ActionResult GetDemo()
        {
            List<string> demo = new List<string>() { "Authorization demo - anonymous" , "test" };
            return Ok(demo);
        }

        [HttpGet("getDemoAuthorized")]
        public ActionResult GetDemoAuthentitaced()
        {
            List<string> demo = new List<string>() { "Authorization demo - auth ok" , "test" };
            return Ok(demo);
        }

        [AllowAnonymous]
        [HttpPost("login")]
        public async Task<ActionResult> LoginAsync([FromBody] LoginModelType loginModelType)
        {
            var user = await _userService.GetUser(loginModelType.Email);
            if (user != null) // We found a user mathing this email address
            {
                var signInResult = await _signInManager.CheckPasswordSignInAsync(user,loginModelType.Password,false);

                if (signInResult.Succeeded)
                {

                    var identity = new ClaimsIdentity(claims : new Claim[]
                         {
                        new Claim(ClaimTypes.Name, loginModelType.Email)
                         },
                         authenticationType : "caloryCookieAuth")
                    {
                    };
                    var claimsPrincipal = new ClaimsPrincipal(identity);

                    await HttpContext.SignInAsync("caloryCookieAuth", claimsPrincipal);

                    return NoContent();
                }
                else
                {
                    return BadRequest("Failed to authenticate user. Try again!");
                }
            }
            return BadRequest("Failed to authenticate user. Try again!");
        }

        [HttpPost("logout")]
        public async Task<ActionResult> LogoutAsync()
        {
            await HttpContext.SignOutAsync();
    return NoContent();
        }

        [HttpPost("register")]
        [AllowAnonymous]
        public async Task<ActionResult> RegisterAsync([FromBody] LoginModelType loginModelType)
        {
            var result = await _userService.RegisterUser(loginModelType.Email, loginModelType.Password);

            if(result.Succeeded)
            {
                return NoContent();
            }
            else
            {
                List<string> errorList = new();
                errorList.Add("Failed to register...");
                foreach(var error in result.Errors)
                {
                    errorList.Add($"Error code: {error.Code} - {error.Description}");
                }

                return BadRequest(new { Result = errorList });
            }
        }
    }

My Program.cs:

 ... 


var builder = WebApplication.CreateBuilder(args);

 ...

builder.Services.AddDbContext<AppDbContext>(o =>
    o.UseSqlServer(builder.Configuration["ConnectionStrings:DefaultConnection"]));

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "caloryCookieAuth";
})
    .AddCookie("caloryCookieAuth", options =>
    {
        options.Cookie.Name = "caloryCookieAuth";
        options.Cookie.SameSite = SameSiteMode.None; //TODO is this important?
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromDays(1);
        options.SlidingExpiration = true;

        options.LoginPath = "/api/User/login";
        options.LogoutPath = "/api/User/logout";

    });


builder.Services.AddDefaultIdentity<IdentityUser>(options => {
     // Configure user identity options here
    options.User.RequireUniqueEmail = true;
})
    .AddEntityFrameworkStores<AppDbContext>();

...


var app = builder.Build();

```...```

app.UseHttpsRedirection();

app.UseCookiePolicy();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();


app.Run();

Edit: I've updated the question to include the whole user controller

CodePudding user response:

If you want to use Cookie.SameSite = SameSiteMode.None in AddCookie(), You need to set Secure = true too, If you don't set the Secure, Cookie.SameSite = SameSiteMode.None will not work and the project will not send your custom cookie, Refer to this code:

.AddCookie("caloryCookieAuth", options =>
    {
        options.Cookie.Name = "caloryCookieAuth";

        //add this to configure Secure

        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.None; //TODO is this important?
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromDays(1);
        options.SlidingExpiration = true;

        options.LoginPath = "/api/User/login";
        options.LogoutPath = "/api/User/logout";

    });

CodePudding user response:

After checking out all of the suggestions I tried one last thing. I specified the authorization scheme for the controller with the [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] tag. Now it seems to work.

  • Related