Authentication CORS POST AngularJS ASP.NET Web API

Topics: ASP.NET Single Page Application
Nov 10, 2013 at 2:40 AM
I'm using John Papa's HotTowel.Angular template for a SPA and added the necessary Web API bits. I can successfully create an account through Web API but not having any luck with logging in. I wonder if something is happening down in the OWIN middleware.

For the Web API part....
In WebApiConfig.cs I have this:
var cors = new EnableCorsAttribute("http://localhost:50493", "*", "GET, POST, OPTIONS, PUT, DELETE"); config.EnableCors(cors);

In Web.config I even added:
<customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="content-type, accept, authorization, origin, referer, user-agent" />
    <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
  </customHeaders>
Down on the client, I am passing in a headers object to the post request:
var headers = {
        'Access-Control-Allow-Origin': 'http://localhost:52635/Token',
        'Access-Control-Allow-Methods': ['GET', 'POST', 'OPTIONS', 'PUT', 'DELETE'],
        'Access-Control-Allow-Headers': 'Content-Type',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    };
The POST call:
var result = "grant_type=password&" + "userName=" + vm.logIn.userName + "&password=" + vm.logIn.password;

$http({
            url: url,
            method: 'POST',
            data: result,
            headers: headers
        }).success(function(data, status, headers, config) {
            accessToken = data.access_token;
        }).error(function(data, status, headers, config) {
            console.log('failed');
        });
The response is 400 Bad Request. The Request/Response headers:

HeadersPreviewResponseTiming
Request URL:http://localhost:52635/Token Request Method:OPTIONS Status Code:400 Bad Request Request Headersview source Accept:*/* Accept-Encoding:gzip,deflate,sdch Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:access-control-allow-origin, accept, access-control-allow-headers, access-control-allow-methods, content-type
Access-Control-Request-Method:POST
Cache-Control:no-cache
Connection:keep-alive
Host:localhost:52635
Origin:http://localhost:50493 Pragma:no-cache Referer:http://localhost:50493/ User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
Response Headersview source
Access-Control-Allow-Headers:content-type, accept, authorization, origin, referer, user-agent
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:*
Cache-Control:no-cache
Content-Length:34
Content-Type:application/json;charset=UTF-8
Date:Sun, 10 Nov 2013 02:36:03 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/8.0
X-Powered-By:ASP.NET
X-SourceFiles:=?UTF-8?B?QzpcUHJvamVjdHNcVlMyMDEzXEF1dGhEZW1vXEF1dGhEZW1vLldlYkFwaVxUb2tlbg==?=
Nov 11, 2013 at 9:18 AM
The login code is using OAuth Authorization Server's token endpoint, which won't go through the pipeline of Web API. So the CORS code you setup for Web API won't work for that. You need to enable OWIN CORS support for that endpoint. You can find the OWIN CORS package at http://www.nuget.org/packages/Microsoft.Owin.Cors/.
Nov 11, 2013 at 9:27 AM
I noticed that you mentioned that you already used OWIN Cors from your email. Could you copy your code here?
The 400 error coming from preflight request. It should be an issue with your CORS settings.
Nov 11, 2013 at 11:08 PM
Edited Nov 11, 2013 at 11:16 PM
This is the latest. I did some poking around when I got home.

Startup.Auth.cs:
I only added one line:
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// added this line
 app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
WebApiConfig.cs
This was the only code I added:
var cors = new EnableCorsAttribute("*", "*", "GET, POST, OPTIONS, PUT, DELETE");
config.EnableCors(cors);
Only one method in the AccountController is CORS enabled:
[AllowAnonymous]
[Route("Register")]
[EnableCors("*", "*", "GET, POST, OPTIONS")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    IdentityUser user = new IdentityUser
    {
        UserName = model.UserName
    };

    IdentityResult result = await UserManager.CreateAsync(user, model.Password);
    IHttpActionResult errorResult = GetErrorResult(result);

    if (errorResult != null)
    {
        return errorResult;
    }

    return Ok();
}
In web.config system.webServer I added this, not sure if it is necessary:
<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, DELETE" />
  </customHeaders>
</httpProtocol>
Pretty sure that is everything I did server side.
Client side, the bits to make the call
var accessToken = "";
var url = "http://localhost:52635/Token";

var headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': ['GET', 'POST', 'OPTIONS', 'PUT', 'DELETE'],
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Credendtials': 'true',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
};

var result = "grant_type=password&" + "userName=" + vm.logIn.userName + "&password=" + vm.logIn.password;

$http({
    url: url,
    method: 'POST',
    data: result,
    headers: headers
}).success(function(data, status, headers, config) {
    accessToken = data.access_token;
}).error(function(data, status, headers, config) {
    console.log('failed');
});
Now the 400 Bad Request OPTIONS is gone. This time I am only getting the Reqeust Headers:

Request URL:http://localhost:52635/Token Request Headersview parsed OPTIONS http://localhost:52635/Token HTTP/1.1
Pragma: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:50493 Referer: http://localhost:50493/ Cache-Control: no-cache User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
Access-Control-Request-Headers: access-control-allow-origin, accept, access-control-allow-headers, access-control-allow-methods, content-type

If you need me to post anything else, just say the word.

Oh here is the list of packages from packages.config:
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Antlr" version="3.4.1.9004" targetFramework="net45" />
  <package id="bootstrap" version="3.0.0" targetFramework="net45" />
  <package id="EntityFramework" version="6.0.0" targetFramework="net45" />
  <package id="jQuery" version="1.10.2" targetFramework="net45" />
  <package id="jQuery.Validation" version="1.11.1" targetFramework="net45" />
  <package id="Microsoft.AspNet.Cors" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Identity.Core" version="1.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Identity.EntityFramework" version="1.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Identity.Owin" version="1.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Mvc" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Razor" version="3.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.1" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Client" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Core" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Cors" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.HelpPage" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.Owin" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebApi.WebHost" version="5.0.0" targetFramework="net45" />
  <package id="Microsoft.AspNet.WebPages" version="3.0.0" targetFramework="net45" />
  <package id="Microsoft.jQuery.Unobtrusive.Validation" version="3.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin" version="2.0.1" targetFramework="net45" />
  <package id="Microsoft.Owin.Cors" version="2.0.1" targetFramework="net45" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Cookies" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Facebook" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Google" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.MicrosoftAccount" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.OAuth" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Owin.Security.Twitter" version="2.0.0" targetFramework="net45" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net45" />
  <package id="Modernizr" version="2.6.2" targetFramework="net45" />
  <package id="Newtonsoft.Json" version="5.0.6" targetFramework="net45" />
  <package id="Owin" version="1.0" targetFramework="net45" />
  <package id="Respond" version="1.2.0" targetFramework="net45" />
  <package id="System.IdentityModel.Tokens.Jwt" version="1.0.0" targetFramework="net45" />
  <package id="Thinktecture.IdentityModel" version="3.6.0" targetFramework="net45" />
  <package id="WebGrease" version="1.5.2" targetFramework="net45" />
</packages>
Nov 27, 2013 at 6:40 AM
Did you get this working?

I am having the same problem, I have installed the OWIN Cors package, and added it to my startup config.

After debugging through Fiddler I have noticed that problem is the browser sends off a CORS OPTIONS Preflight request first when POSTing to the /Token url which the browser strips out the username/password/granttype data, the Preflight response is not sent back to the Browser first so it can resend the original POST with the username/password/granttype data.

As a test, I setup another api action and could successfully POST to that with the OWIN CORS correctly replying to the CORS OPTIONS Preflight request first, then allowing the browser to send the original request.

You do not need to add all of the custom CORS request/response headers in the web.config or the $.ajax request as the browser and OWIN COrs package handle that for you. I also removed the WebApi CORS package as it was adding duplicate headers along with the OWIN CORs packge. I assume you only need one of them.
Nov 27, 2013 at 2:02 PM
Yeah I did end up getting it working.
Nov 27, 2013 at 11:14 PM
I finally got it working.

There is a Trick to it. You need to configure the Cors before the UseBearerToken

e.g.
        public void Configuration(IAppBuilder app)
        {
            // This must come first to intercept the /Token requests
            app.UseCors(corsOptions);

            app.UseOAuthBearerTokens(OAuthOptions);
        }
Mar 4, 2014 at 12:24 AM
Any idea how to fix this issue? I am using OWIN CORS as per madaz last post and it doesn't work (no webapi CORS, no web.config CORS), when using Chrome or FF to POST to /Token I always get an OPTIONS request that stripes out the user, password and grant_type so I never get the token back.

Regards,