Ужасная проблема CORS с WebAPI и токеном

Клянусь, это случалось со мной так много раз, что я на самом делененавидеть CORS. Я только что разделил свое приложение на две части, так что одна обрабатывает только стороны API, а другая обрабатывает вещи на стороне клиента. Я делал это раньше, поэтому я знал, что мне нужно убедиться, что CORS был включен и разрешил все, поэтому я настроил это в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 }
    );
}

Как видите, моя первая строка включает CORS, поэтому она должна работать. Если я открываю клиентское приложение и запрашиваю API, оно действительно работает (без EnableCors я получаю ожидаемую ошибку CORS. Проблема в том, что я/ маркер все еще получает ошибку CORS. Теперь я знаю, что конечная точка / token не является частью WebAPI, поэтому я создал свой собственный OAuthProvider (который я должен отметить, что он отлично используется в других местах), и это выглядит так:

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);
    }
}

Как вы можете видеть, вGrantResourceOwnerCredentials метод я снова включаю CORS доступ ко всему. Это должно работать для всех запросов к / токену, но это не так. Когда я пытаюсь войти в систему из своего клиентского приложения, я получаю сообщение об ошибке CORS. Chrome показывает это:

XMLHttpRequest не может загрузитьHTTP: // локальный: 62605 / маркер, Ответ на запрос предварительной проверки не проходит проверку контроля доступа: в запрашиваемом ресурсе отсутствует заголовок «Access-Control-Allow-Origin». ПроисхождениеHTTP: // локальный: 50098Поэтому не допускается доступ. Ответ имел HTTP-код состояния 400.

и Firefox показывает это:

Блокирован перекрестный запрос: одна и та же политика происхождения запрещает чтение удаленного ресурса вHTTP: // локальный: 62605 / маркер, (Причина: отсутствует заголовок CORS «Access-Control-Allow-Origin»). Блокирован перекрестный запрос: одна и та же политика происхождения запрещает чтение удаленного ресурса вHTTP: // локальный: 62605 / маркер, (Причина: запрос CORS не выполнен).

В целях тестирования я решил использовать fiddler, чтобы увидеть, смогу ли я увидеть что-то еще, что может дать мне подсказку о том, что происходит. Когда я пытаюсь войти, FIddler показывает код ответа как 400, и если я смотрю на необработанный ответ, я вижу ошибку:

{"error":"unsupported_grant_type"}

что странно, потому что данные, которые я отправляю, не изменились и работали до раскола. Я решил использовать Composer на fiddler и повторил то, что, как я ожидаю, будет выглядеть запрос POST. Когда я выполняю его, он работает нормально, и я получаю код ответа 200.

У кого-нибудь есть идеи, почему это может происходить?

Обновление 1

Просто для справки, запрос от моего клиентского приложения выглядит так:

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

от композитора это выглядит так:

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)

Ваш ответ на вопрос