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.