Scary CORS issue with WebAPI and token

I swear it is so many times to me that I really hate CORS. I just split my application into two in order to process only part of the API, and the other - the client part. I did this before, so I knew that I needed to make sure CORS is enabled and enabled, so I installed it in WebApiConfig.cs

public static void Register(HttpConfiguration config) { // Enable CORS config.EnableCors(new EnableCorsAttribute("*", "*", "*")); // Web API configuration and services var formatters = config.Formatters; var jsonFormatter = formatters.JsonFormatter; var serializerSettings = jsonFormatter.SerializerSettings; // Remove XML formatting formatters.Remove(config.Formatters.XmlFormatter); jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); // Configure our JSON output serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); serializerSettings.Formatting = Formatting.Indented; serializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; serializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None; // Configure the API route config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } 

As you can see, my first line includes CORS, so it should work. If I open my client application and request the API, it really works (without EnableCors I get the expected CORS error. The problem is that my / token still gets the CORS error. Now I know that the endpoint / token is not part of the WebAPI, so I created my own OAuthProvider (which, as I should note, used elsewhere just fine), and looks like this:

 public class OAuthProvider<TUser> : OAuthAuthorizationServerProvider where TUser : class, IUser { private readonly string publicClientId; private readonly UserService<TUser> userService; public OAuthProvider(string publicClientId, UserService<TUser> userService) { if (publicClientId == null) throw new ArgumentNullException("publicClientId"); if (userService == null) throw new ArgumentNullException("userService"); this.publicClientId = publicClientId; this.userService = userService; } public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); var user = await this.userService.FindByUserNameAsync(context.UserName, context.Password); if (user == null) { context.SetError("invalid_grant", "The user name or password is incorrect."); return; } var oAuthIdentity = this.userService.CreateIdentity(user, context.Options.AuthenticationType); var cookiesIdentity = this.userService.CreateIdentity(user, CookieAuthenticationDefaults.AuthenticationType); var properties = CreateProperties(user.UserName); var ticket = new AuthenticationTicket(oAuthIdentity, properties); context.Validated(ticket); context.Request.Context.Authentication.SignIn(cookiesIdentity); } public override Task TokenEndpoint(OAuthTokenEndpointContext context) { foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) context.AdditionalResponseParameters.Add(property.Key, property.Value); return Task.FromResult<object>(null); } public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { // Resource owner password credentials does not provide a client ID. if (context.ClientId == null) { context.Validated(); } return Task.FromResult<object>(null); } public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) { if (context.ClientId == this.publicClientId) { var redirectUri = new Uri(context.RedirectUri); var expectedRootUri = new Uri(context.Request.Uri, redirectUri.PathAndQuery); if (expectedRootUri.AbsoluteUri == redirectUri.AbsoluteUri) context.Validated(); } return Task.FromResult<object>(null); } public static AuthenticationProperties CreateProperties(string userName) { IDictionary<string, string> data = new Dictionary<string, string> { { "userName", userName } }; return new AuthenticationProperties(data); } } 

As you can see, in the GrantResourceOwnerCredentials method , I again allow access to all CORS. This should work for all requests / tokens, but it is not. When I try to log in from my client application, I get a CORS error. Chrome shows this:

XMLHttpRequest cannot load http: // localhost: 62605 / token . The response to the request before the flight does not pass the access control check. There is no "Access-Control-Allow-Origin" header on the requested resource. Origin ' http: // localhost: 50098 ' is therefore not allowed. The response was an HTTP 400 status code.

and Firefox shows this:

The cross-request request is blocked: a policy of the same origin prohibits reading the remote resource at http: // localhost: 62605 / token . (Reason: CORS header "Access-Control-Allow-Origin" is missing). The cross-request request is blocked: a policy of the same origin prohibits reading the remote resource at http: // localhost: 62605 / token . (Reason: CORS request failed).

For testing purposes, I decided to use a violinist to see if I could see anything else that could make me understand what was going on. When I try to log in, FIddler shows the response code as 400, and if I look at the original answer, I can see the error:

 {"error":"unsupported_grant_type"} 

which is strange, because the data that I send did not change and worked perfectly before separation. I decided to use Composer on the violin and reproduce what I expect the POST request should look like. When I execute it, it works fine and I get a 200 response code.

Can anyone understand why this could happen?

Update 1

Just for reference, a request from my client application is as follows:

 OPTIONS http://localhost:62605/token HTTP/1.1 Host: localhost:62605 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Access-Control-Request-Method: POST Origin: http://localhost:50098 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36 Access-Control-Request-Headers: accept, authorization, content-type Accept: */* Referer: http://localhost:50098/account/signin Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8 

from the composer, it looks like this:

 POST http://localhost:62605/token HTTP/1.1 User-Agent: Fiddler Content-Type: 'application/x-www-form-urlencoded' Host: localhost:62605 Content-Length: 67 grant_type=password&userName=foo&password=bar 
+5
source share
5 answers

It turned out that there were no problems with CORS at all. I had an interceptor class that changed headers incorrectly. I suggest that you contact someone else with these problems in the future, if you have CORS configured either in WebConfig.cs, or in your Startup class, or even in web.config, then you need to check that nothing changes your headers. If so, turn it off and check again.

+1
source

Inside

  public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) 

Get rid of this:

  context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); 

You are currently doing the CORS thing twice. Once with .EnableCors, and again by writing a title at the endpoint of the marker.

For what it's worth, in my OWIN start class I have it at the very top:

 app.UseCors(CorsOptions.AllowAll); 

I also DO NOT have it in my WebAPI registration method, since I allow it to run OWIN.

+6
source

@ r3plica

I had this problem and it looks like Bill said.

Put the line "app.UseCors" at the very top in configuration mode () (until ConfigureOAuth (application) is enough)

Example:

 public void Configuration(IAppBuilder app) { app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); HttpConfiguration config = new HttpConfiguration(); ConfigureWebApi(config); ConfigureOAuth(app); app.UseWebApi(config); } 
+3
source

Since OAuthAuthorizationServer runs as Owin middleware, you must use the appropriate Microsoft.Owin.Cors package to enable CORS, which works with any middleware in the pipeline. Keep in mind that WebApi and Mvc are middleware itself regarding the owin pipeline.

So delete config.EnableCors(new EnableCorsAttribute("*", "*", "*")); from your WebApiConfig and add the following to your startup class. Note app.UseCors must precede app.UseOAuthAuthorizationServer

 app.UseCors(CorsOptions.AllowAll) 
+2
source

We encountered a similar situation and specified some CORS data in the system.webServer node file for web.config to pass the preflight check. Your situation is slightly different from ours, but perhaps this will also help you.

Here we added:

 <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> <add name="Access-Control-Allow-Credentials" value="true" /> <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS" /> </customHeaders> </httpProtocol> 
+1
source

Source: https://habr.com/ru/post/1234190/


All Articles