web-dev-qa-db-fra.com

Activer l'authentification par jeton CORS pour Web Api 2 et OWIN

J'ai un projet Web ASP.NET MVC 5 (localhost: 81) qui appelle des fonctions de mon projet WebApi 2 (localhost: 82) à l'aide de Knockoutjs, pour établir la communication entre les deux projets, j'active CORS. Tout fonctionne jusqu'à présent jusqu'à ce que j'essaie d'implémenter l'authentification par jeton OWIN sur WebApi.

Pour utiliser le point de terminaison/token sur le WebApi, je dois également activer CORS sur le point de terminaison, mais après des heures d'essais et de recherche de solutions, il fonctionne toujours et l'api/token se traduit toujours par:

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

MODIFIER:

1. Remarque importante: l'ouverture directe du point de terminaison (localhost: 82/token) fonctionne.

2. L'appel de l'API (localhost: 82/api/..) à partir du webproject fonctionne également, donc le CORS est activé pour WebApi.

27
Sam

Je sais que votre problème a été résolu dans les commentaires, mais je crois qu'il est important de comprendre ce qui en est la cause et comment résoudre toute cette classe de problèmes.

En regardant votre code, je peux voir que vous définissez le Access-Control-Allow-Origin en-tête plusieurs fois pour le point de terminaison du jeton:

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

Et à l'intérieur de la méthode GrantResourceOwnerCredentials:

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

Cela, en regardant les spécifications CORS , est lui-même un problème car:

Si la réponse comprend zéro ou plusieurs valeurs d'en-tête Access-Control-Allow-Origin, renvoyer échouer et terminer cet algorithme.

Dans votre scénario, le framework définit cet en-tête deux fois et, en comprenant comment CORS doit être implémenté, cela entraînera la suppression de l'en-tête dans certaines circonstances (éventuellement liées au client).

Ceci est également confirmé par la réponse à la question suivante: Duplicate Access-Control-Allow-Origin: * provoquant une erreur COR?

Pour cette raison, déplacer l'appel vers app.UseCors après l'appel à ConfigureOAuth permet à votre en-tête CORS d'être défini une seule fois (car le pipeline owin est interrompu au niveau du middleware OAuth, et n'atteint jamais le middleware Microsoft CORS pour le point de terminaison Token) et fait fonctionner votre appel Ajax.

Pour une solution meilleure et globale, vous pouvez essayer de mettre à nouveau app.UseCors avant l'appel OAuth middleware, et supprimez le deuxième Access-Control-Allow-Origin insertion dans GrantResourceOwnerCredentials.

42
Federico Dipuma

Suivez les étapes ci-dessous et votre API fonctionnera:

  1. Remove tout code comme config.EnableCors(), [EnableCors(header:"*"....)] de votre API.
  2. Allez dans startup.cs et ajouter sous la ligne

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

avant

    ConfigureAuth(app);

Uou devra également installer le package Microsoft.owin.cors pour utiliser cette fonctionnalité

9
santosh kumar

Résoudre le problème sans utiliser app.UseCors ()

J'ai eu le même problème. J'ai utilisé un client Vue.Js avec axois pour accéder à mon API REST avec cross-corps. Sur mon serveur Owin-Api, j'étais impossible d'ajouter Microsoft.Owin.Cors nuget en raison de conflits de version avec d'autres composants tiers. Donc, je ne pouvais pas utiliser la méthode app.UseCors () mais je l'ai résolu en utilisant le pipeline du middleware.

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

J'ai donc créé mon propre middleware et manipulé la réponse. Les appels GET n'avaient besoin que des en-têtes Access-Control-Allow, tandis que pour les appels OPTIONS, j'avais également besoin de manipuler le StatusCode car axois.post () appelait d'abord avec la méthode OPTIONS avant d'envoyer le POST. Si OPTIONS retourne le StatusCode 405, le POST ne sera jamais envoyé.

Cela a résolu mon problème. Peut-être que cela peut aussi aider quelqu'un.

0
jaz