1

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?

5
  • 2
    Your login method only accepts an HTTP POST but a redirect will be a GET request. Commented Nov 20, 2018 at 1:23
  • @DavidG: I successfully logged in with a POST before calling the API. I'm updating my post with the network calls I got from Edge > Developer Tools. Commented Nov 20, 2018 at 1:30
  • Q: If all I needed was an [HttpGet] (I'm new to Asp.Net Core - don't disbelieve you), then how exactly would I do it? Could you give me a "response" that points me in the right direction? Commented Nov 20, 2018 at 1:39
  • 1
    Try to specify auth schema by changing attribute to [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]. You are trying to authorize the web api using cookies, it may fails without additional configurations Commented Nov 20, 2018 at 11:42
  • 1
    Generally, PasswordSignInAsync is used for cookie authentication, if you have only web api, maybe you need to move to the JWT tokens Commented Nov 20, 2018 at 12:12

2 Answers 2

3

If I understand you meant correctly, the problem may come from 2 things:

  1. You're trying to access to /api/ManageCar without login. The attribute [Authorize] means: This controller/action requires login before assigning to.

That's why it redirected to the path: /Account/Login?ReturnUrl=%2Fapi%2FManageCar

You can check the path, there are 2 parts:

  • The first part is: /Account/Login. This is the url of the login page.

  • The second part is: ?ReturnUrl=%2Fapi%2FManageCar. We can understand it like: ?ReturnUrl=/api/ManageCar because %2F stands for /. This parameter query string means: after login successful, the request will be redirected to /api/ManaCar.

    1. The second problem may be: In the Get method, you're setting it as a GET method via using [HttpGet]. That means this method can only be assigned to via using GET method. So, if you're trying to make a POST request, it would not work.

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

If you're using jquery, after login successful, you can try to make a GET request like this:

$.get('/api/ManageCar').done(function (data) {
    console.log(data);
});

Or changing [HttpGet] attribute to [HttpPost] attribute:

$.post('/api/ManageCar').done(function (data) {
    console.log(data);
});
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. Let me explain the problem differently: See my updates above.
@FoggyDay Sorry for late, my mom needs help :) In your case, I can only think about Cookie. The cookie has not been set. So, after login successful, the request is still not authorized
2

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.

2 Comments

I'm new to "routing", and there's much I still don't understand. For example, Q: why exactly does Asp.Net Core invoke my AccountController just because of [Authorize]? I didn't explicitly specify that anywhere; it's "implicit". Q: Do I need to do "something else" in my AccountController for this to work?
It's working now. I tried many, many things but, unfortunately, I don't know exactly "what fixed it". It definitely had to do with "routing". Thank you for your help.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.