Включить аутентификацию маркера CORS для Web Api 2 и OWIN

У меня есть веб-проект ASP.NET MVC 5 (localhost: 81), который вызывает функции из моего проекта WebApi 2 (localhost: 82) с помощью Knockoutjs, чтобы связь между двумя проектами включала CORS. Все работает до тех пор, пока я не попытался внедрить аутентификацию токена OWIN в WebApi.

Чтобы использовать конечную точку/токена в WebApi, мне также необходимо включить CORS на конечной точке, но после нескольких часов попыток и поиска решений она все еще работает, и api/token все еще приводит к:

XMLHttpRequest cannot load http://localhost:82/token. No 'Access-Control-Allow-Origin' header is present on the requested resource. 

public void Configuration(IAppBuilder app)
{
    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    TokenConfig.ConfigureOAuth(app);
    ...
}

TokenConfig

public static void ConfigureOAuth(IAppBuilder app)
{
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);

    OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new SimpleAuthorizationServerProvider()
    };

    app.UseOAuthAuthorizationServer(OAuthServerOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}

AuthorizationProvider

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
    context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

    var appUserManager = context.OwinContext.GetUserManager<AppUserManager>();
    IdentityUser user = await appUserManager.FindAsync(context.UserName, context.Password);

    if (user == null)
    {
        context.SetError("invalid_grant", "The user name or password is incorrect.");
        return;
    }
    ... claims
}

IdentityConfig

public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
    // Tried to enable it again without success. 
    //context.Response.Headers.Add("Access-Control-Allow-Origin", new[] {"*"});

    var manager = new AppUserManager(new UserStore<AppUser>(context.Get<ApplicationDbContect>()));

    ...

    var dataProtectionProvider = options.DataProtectionProvider;
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider =
                new DataProtectorTokenProvider<AppUser>(dataProtectionProvider.Create("ASP.NET Identity"));
    }
    return manager;
}

EDIT:

1. Важно отметить, что открытие конечной точки напрямую (localhost: 82/token) работает.

2. Вызов Api (localhost: 82/api/..) из веб-проекта также работает, поэтому CORS включен для WebApi.

Ответы

Ответ 1

Я знаю, что ваша проблема была решена внутри комментариев, но я считаю важным понять, что вызывает ее и как решить весь этот класс проблем.

Посмотрев на свой код, я вижу, что вы устанавливаете заголовок Access-Control-Allow-Origin более одного раза для конечной точки Token:

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

И внутри метода GrantResourceOwnerCredentials:

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

Это, глядя на спецификации CORS, само по себе является проблемой, потому что:

Если ответ включает в себя ноль или несколько значений заголовка Access-Control-Allow-Origin, возвратите сбой и завершите этот алгоритм.

В вашем сценарии среда устанавливает этот заголовок два раза, и понимание того, как CORS необходимо реализовать, приведет к удалению заголовка в определенных обстоятельствах (возможно, связанных с клиентом).

Это также подтверждается следующим ответом на вопрос: Дублировать Access-Control-Allow-Origin: * вызывает ошибку COR?

По этой причине перенос вызова на app.UseCors после вызова ConfigureOAuth позволяет настроить заголовок CORS только один раз (поскольку конвейер owin прерван в промежуточном программном обеспечении OAuth и никогда не достигает промежуточного программного обеспечения Microsoft CORS для Token) и заставляет ваш вызов Ajax работать.

Для лучшего и глобального решения вы можете попробовать снова поставить app.UseCors перед вызовом промежуточного программного обеспечения OAuth и удалить вторую вставку Access-Control-Allow-Origin внутри GrantResourceOwnerCredentials.

Ответ 2

Выполните следующие шаги, и ваш API будет работать:

  1. Удалите любой код, например config.EnableCors(), [EnableCors(header:"*"....)] из вашего API.
  2. Зайдите в startup.cs и добавьте строку ниже

    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    

до

    ConfigureAuth(app);

Вам также необходимо установить пакет Microsoft.owin.cors, чтобы использовать эту функциональность.

Ответ 3

Решение проблемы без использования app.UseCors()

У меня такая же проблема. Я использовал клиент Vue.Js с axois, чтобы получить доступ к моему REST-API с помощью кросс-корпуса. На своем сервере Owin-Api я не смог добавить nuget Microsoft.Owin.Cors из-за конфликта версий с другими сторонними компонентами. Поэтому я не смог использовать метод app.UseCors(), но решил это с помощью промежуточного программного обеспечения.

private IDisposable _webServer = null;

public void Start(ClientCredentials credentials)
{
    ...
    _webServer = WebApp.Start(BaseAddress, (x) => Configuration(x));
    ...
}

public void Configuration(IAppBuilder app)
{
    ...
    // added middleware insted of app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    app.Use<MyOwinMiddleware>();
    app.UseWebApi(config);
    ...
}

public class MyOwinMiddleware : OwinMiddleware
{
    public MyOwinMiddleware(OwinMiddleware next) :
        base(next)
    { }

    public override async Task Invoke(IOwinContext context)
    {
        var request = context.Request;
        var response = context.Response;

        response.OnSendingHeaders(state =>
        {
            var resp = (IOwinResponse)state;

            // without this headers -> client apps will be blocked to consume data from this api
            if (!resp.Headers.ContainsKey("Access-Control-Allow-Origin"))
                resp.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            if (!resp.Headers.ContainsKey("Access-Control-Allow-Headers"))
                resp.Headers.Add("Access-Control-Allow-Headers", new[] { "*" });
            if (!resp.Headers.ContainsKey("Access-Control-Allow-Methods"))
                resp.Headers.Add("Access-Control-Allow-Methods", new[] { "*" });

            // by default owin is blocking options not from same origin with MethodNotAllowed
            if (resp.StatusCode == (int)HttpStatusCode.MethodNotAllowed &&
                HttpMethod.Options == new HttpMethod(request.Method))
            {
                resp.StatusCode = (int)HttpStatusCode.OK;
                resp.ReasonPhrase = HttpStatusCode.OK.ToString();
            }

        }, response);

        await Next.Invoke(context);
    }
}

Итак, я создал свое промежуточное программное обеспечение и манипулировал ответом. Для вызовов GET нужны только заголовки Access-Control-Allow, тогда как для вызовов OPTIONS мне также нужно было манипулировать StatusCode, потому что axois.post() сначала вызывает метод OPTIONS перед отправкой POST. Если OPTIONS возвращает StatusCode 405, сообщение POST никогда не будет отправлено.

Это решило мою проблему. Может быть, это тоже может кому-то помочь.