title: How to use [Authorize] and antiforgery in a View-less WebAPI ASP.NET Core app?
body: <p>I'm having trouble using <code>[Authorize]</code> annotations in a strict (ie, <code>View</code>less) ASP.NET Core WebAPI project when I can't guarantee what platform the client will use. That is, the app needs to be a true API that doesn't require a specific platform to access.</p>
  
  <blockquote>
    <p>Note: When I say, "strict WebAPI", my project actually started life as an MVC project generated by...</p>
  
  <pre><code>dotnet new mvc --auth Individual
  </code></pre>
    
    <p>... from which I immediately deleted all the views, etc, and changed the routing preferences to match WebAPI conventions.</p>
  </blockquote>
  
  <hr>
  
  <h3>What I'm trying</h3>
  
  <p>When I access a standard login function (stripped down to the essentials in this paste, below) via AJAX, I get a JSON payload and one cookie returned.</p>
  
  <pre><code>[HttpPost("apiTest")]
  [AllowAnonymous]
  public async Task&lt;IActionResult&gt; ApiLoginTest([FromBody] LoginViewModel model, string returnUrl = null)
  {
      object ret = new { Error = "Generic Error" };
  
      if (ModelState.IsValid)
      {
          var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
          if (result.Succeeded)
              ret = new { Success = true };
          else
              ret = new { Error = "Invalid login attempt" };
      }
  
      return new ObjectResult(ret);
  }
  </code></pre>
  
  <p>On success, that returns a cookie similar to the following:</p>
  
  <pre><code>.AspNetCore.Identity.Application=CfDJ8Ge9E-[many characters removed]; path=/; domain=localhost; HttpOnly; Expires=Fri, 16 Mar 2018 16:27:47 GMT;
  </code></pre>
  
  <hr>
  
  <h3>Issue</h3>
  
  <p>After a seemingly successful login, I try to access two API endpoints that do exactly the same thing, one annotated <code>AllowAnonymous</code> and one <code>Authorized</code>:</p>
  
  <pre><code>private IActionResult _getStatus()
  {
      object ret = new { Error = "Generic Error" };
  
      var isSignedIn = _signInManager.IsSignedIn(User);
      var userName = _userManager.GetUserName(User);
  
      return new ObjectResult(
          new {
              SignedIn = isSignedIn,
              Name = userName
          }
      );
  }
  
  [HttpGet("authorizedTest")]
  [Authorize]
  public IActionResult GetCurrentLoginInfo2()
  {
      return _getStatus();
  }
  
  [HttpGet("anonymousTest")]
  [AllowAnonymous]
  public IActionResult GetCurrentLoginInfo()
  {
      return _getStatus();
  }
  </code></pre>
  
  <p>The <code>anonymousTest</code> endpoint is accessible before and after login, though it tells me I'm not logged in (<code>SignedIn</code> is <code>false</code>) even after login. The <code>authorizedTest</code> endpoint is never accessible.</p>
  
  <p>My guess is that the single cookie is not enough to get past an <code>[Authorized]</code> tag. I <em>believe</em> I also need an antiforgery value, either from a hidden value in a form generated by <code>@Html.AntiForgeryToken()</code> or from a second cookie that <code>View</code>s seem to send by default. That cookie looks like this...</p>
  
  <pre><code>.AspNetCore.Antiforgery.0g4CU0eoNew=CfDJ8Ge9E-[many characters removed]; path=/; domain=localhost; HttpOnly; Expires=Tue, 19 Jan 2038 03:14:07 GMT;
  </code></pre>
  
  <hr>
  
  <h3>Failed solutions</h3>
  
  <p>I've seen lots of answers for how to use pure AJAX that basically say "<a href="https://stackoverflow.com/questions/4074199/jquery-ajax-calls-and-the-html-antiforgerytoken">get the anti-forgery from the hidden form</a>" or "<a href="https://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc/11726560#11726560">read it from the headers</a>", but I don't have a <code>View</code>; there's no hidden form. Nor do I really want to kludge <a href="https://stackoverflow.com/questions/6656999/mvc-generating-an-antiforgerytoken-for-a-ajax-response-to-the-client#comment7899862_6656999">sending down a partial view for the clients to scrape</a>.</p>
  
  <p>The best answer I've seen is <a href="https://stackoverflow.com/questions/16192019/antiforgery-gettokens-what-is-the-purpose-of-the-oldcookietoken-parameter">this one talking about using iOS</a>. The situation seems analogous:</p>
  
  <blockquote>
    <p>Because we're not delivering HTML to the client, we can't use the standard <code>@Html.AntiForgeryToken()</code>, so instead we have to use <code>AntiForgery.GetTokens</code> to acquire and distribute the tokens to our clients. </p>
  </blockquote>
  
  <p>But even though my Intellisense "sees" <code>AntiForgery.GetTokens</code>, it won't compile, even after grabbing what seems to be the right nuget package: </p>
  
  <pre><code>dotnet add package microsoft-web-helpers --version 2.1.20710.2
  </code></pre>
  
  <hr>
  
  <p>My question, then, is: <strong>How do I use Antiforgery <em>outside of Razor</em> but using ASP.NET Core to create an Identity-restricted WebAPI-style project?</strong></p>
  
  <hr>
  
  <h3>Why I think it's an anti-forgery issue</h3>
  
  <p>For a while, I couldn't figure out why my code didn't work in a strict WebAPI project, but did when transplanted into the standard identity ASP.NET Core project's (one created from <code>dotnet new mvc --auth Individual</code>) <code>AccountController</code>. </p>
  
  <p>The key? <code>options.LoginPath</code>. When I'm in WebAPI land, I forward to <em>an API endpoint</em> to log in:  </p>
  
  <p><code>options.LoginPath = "/api/accounts/requestLogin";</code>  </p>
  
  <p>In the stock project, it's a <code>View</code> by default, <em>which provides the antiforgery cookie</em> on load:</p>
  
  <p><code>options.LoginPath = "/Account/Login"; // As soon as Login.cshtml loads, BAM. .AspNetCore.Antiforgery cookie.</code></p>
  
  <p>Strangely, sometimes I can delete the second, <code>AspNetCore.Antiforgery</code> cookie within Postman and still access an <code>[Authorize]</code>-annotated method, so I'm not 100% sure I'm not barking up the wrong tree here, but this is my best lead so far...</p>