Search code examples
c#asp.net-coreasp.net-web-api2identity

ASP.Net Core 2.1/WebAPI app: "HTTP 404 not found" calling a REST url with [Authorize]


I'm working through a "hello world" tutorial on Asp.Net Core. I'm using WebApi (not MVC).

Here is the controller for the REST API I'm trying to invoke:

...
[Authorize]
[Route("api/[controller]")]
[ApiController]

public class ManageCarController : ControllerBase
{
    private IMapper mapper;
    private ApplicationDbContext dbContext;

    public ManageCarController(IMapper mapper, ApplicationDbContext dbContext)
    {
        this.mapper = mapper;
        this.dbContext = dbContext;
    }

    // GET api/values
    [HttpGet]
    public IEnumerable<CarViewModel> Get()
    {
        IEnumerable<CarViewModel> list =
            this.mapper.Map<IEnumerable<CarViewModel>>(this.dbContext.cars.AsEnumerable());
    return list;
}
...

Here is my controller for Login:

...
[Authorize]
[Route("[controller]/[action]")]
public class AccountController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly ILogger _logger;

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        ILogger<AccountController> logger)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
    }

    [TempData]
    public string ErrorMessage { get; set; }
    [HttpPost]
    [AllowAnonymous]
    public async Task<IActionResult> Login([FromBody]LoginViewModel model)
    {
        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
                (model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
            if (result.Succeeded)
            {
                var msg = "User logged in.";
                return Ok(msg);
            }
        }
        // If we got this far, something failed, redisplay form
        return BadRequest("Fail to login with this account");
    }

I can log in (http://localhost:5000/Login) OK, the response is "User logged in."

When I browse to http://localhost:5000/api/ManageCar, it redirects here and gives me an HTTP 404: https://localhost:44342/Account/Login?ReturnUrl=%2Fapi%2FManageCar, and I never hit the controller.

If I comment out [Authorize], then http://localhost:5000/api/ManageCar works OK.

What am I missing?

More importantly, what is a good way to troubleshoot the problem?


Update

  1. Prior to calling http://localhost:5000/api/ManageCar, I first log in (successfully).

  2. Here is what I see in Edge > Developer Tools > Network:

     Name    Protocol    Method  Result  Content type    Received    Time    Initiator
     https://localhost:44342/Account/Login   HTTP/2  POST    200 application/json        9.31 s  XMLHttpRequest
       <= Login: OK
     https://localhost:44342/Account/Login   HTTPS   GET 200     (from cache)    0 s 
       <= ManageCars (GET@1): OK
     https://localhost:44342/api/ManageCar   HTTP/2  GET 302     0 B 97.43 ms    XMLHttpRequest
       <= ManageCars (GET@2 - 302 redirect to REST API): OK
     https://localhost:44342/Account/Login?ReturnUrl=%2Fapi%2FManageCar  HTTP/2  GET 404     0 B 16.77 ms    XMLHttpRequest
       <= ManageCars (GET@3 - 404: not found): FAILS
          - Console:
     HTTP 404: NOT FOUND - The server has not found anything matching the requested URI (Uniform Resource Identifier).
     (XHR)GET - https://localhost:44342/Account/Login?ReturnUrl=%2Fapi%2FManageCar
    

CLARIFICATION FOR Tân Nguyễn's RESPONSE:

  1. I have a REST API, written in C# using Asp.Net Core 2.1 + Web API.
  2. The API has a "GET" method, /api/ManageCar. If I call with without [Authorize], it works.
  3. I'm "securing" the API with Asp.Net Core Identity. The URL is '/Account/Login'. It needs to use POST (to pass username and password). That works, too.
  4. If I annotate "ManageCar" with [Authorize], and then log in (successfully), then THEN GET /api/ManageCar ... it DOESN'T go directly to my controller for "/api/ManageCar".
  5. Instead, it goes to "/Account/Login" (I'm already logged in, the result is HTTP 200), then redirects to "https://localhost:44342/Account/Login?ReturnUrl=%2Fapi%2FManageCar"/
  6. I should be able to do a POST for my login, and a GET for my (now authenticated) query - it should "just work".
  7. Unfortunately, I don't know what Asp.Net is doing "behind the scenes" ... and I don't know what's causing the problem, or how to fix it.

Update

  1. I still haven't resolved the problem - I'm still getting HTTP 404 with [Authorize], and it works without [Authorize]

  2. Both my AccountController and ManageCarController have the same path: [Route("api/[controller]/[action])]' and [Route("api/[controller])]`, respectively. I can still log in successfully, I still get HTTP 404 when I try to read the "Cars" list.

  3. I enabled "Trace" logging in my appsettings.json. Here is a summary of the output of the failed API call:

     Console log:
     - Request starting HTTP/1.1 GET http://localhost:63264/api/ManageCar  
       Request finished in 81.994ms 302
     - Request starting HTTP/1.1 GET http://localhost:63264/Account/Login?ReturnUrl=%2Fapi%2FManageCar 
       AuthenticationScheme: Identity.Application was successfully authenticated.
       The request path /Account/Login does not match a supported file type
       The request path  does not match the path filter
       Request finished in 31.9471ms 404
    

    Summary:

    a) request to "ManageCar" redirects to AccountController => OK
    b) AccountController gets the request => OK
    c) Q: Does AccountController authenticate the request? <= it seems to ("successfully authenticated"...)
    d) Q: What do "match a supported file type" or "match the path filter" mean?
    What can I do about them?


Solution

  • I think that is a problem of routing, can you verify your routes. do you notice that the two controllers have two routes every one [Route("[controller]/[action]")] and [Route("api/[controller]")].

    If your routes are OK, you should check your authentication mechanism. How do you check if user is authenticated and how to redirect because you don't need to be redirected to your login method in every Api method if you are already authenticated.

    Thanks.