Activation des demandes multi-origines (CORS) dans ASP.NET Core

Notes

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version ASP.NET Core 8.0 de cet article.

Par Rick Anderson et Kirk Larkin

Cet article montre comment Cross-Origin Resource Sharing (CORS) est activé dans une application ASP.NET Core.

La sécurité du navigateur empêche une page Web d’envoyer des requêtes à un domaine différent de celui qui a servi la page Web. Cette restriction est appelée stratégie de même origine. La politique de la même origine empêche un site malveillant de lire les données sensibles d'un autre site. Parfois, vous souhaiterez peut-être autoriser d’autres sites à effectuer des demandes cross-origin à votre application. Pour plus d’informations, consultez l’article Mozilla CORS.

CORS (Cross Origin Resource Sharing) :

  • C’est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
  • N’est pas une fonctionnalité de sécurité, CORS assouplit la sécurité. Une API n’est pas plus sûre en autorisant CORS. Pour plus d'informations, consultez la section Fonctionnement de CORS.
  • Permet à un serveur d’autoriser explicitement certaines demandes multi-origines tout en refusant d’autres.
  • Est plus sûr et plus flexible que les techniques précédentes telles que JSONP.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Même origine

Deux URL ont la même origine si elles ont des schémas, des hôtes et des ports (RFC 6454) identiques.

Ces deux URL ont la même origine :

  • https://example.com/foo.html
  • https://example.com/bar.html

Ces URL ont des origines différentes des deux URL précédentes :

  • https://example.net : Domaine différent
  • https://www.example.com/foo.html : Sous-domaine différent
  • http://example.com/foo.html : Schéma différent
  • https://example.com:9000/foo.html : Port différent

Activez CORS

Il y a trois manières pour activer CORS :

L’utilisation de l’attribut [EnableCors] avec une stratégie nommée fournit le meilleur contrôle pour limiter les points de terminaison qui prennent en charge CORS.

Avertissement

UseCors doit être appelé dans le bon ordre. Pour plus d’informations, consultez Ordre des intergiciels (middleware). Par exemple, UseCors doit être appelé avant UseResponseCaching lors de l’utilisation de UseResponseCaching.

Chaque approche est détaillée dans les sections suivantes.

CORS avec une stratégie et un intergiciel nommés

CORS Middleware gère les demandes cross-origin. Le code suivant applique une stratégie CORS à tous les points de terminaison de l’application avec les origines spécifiées :

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent :

Avec le routage de point de terminaison, l’intergiciel CORS doit être configuré pour s’exécuter entre les appels à UseRouting et UseEndpoints.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

L’appel de méthode AddCors ajoute des services CORS au conteneur de service de l’application :

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour plus d’informations, consultez Options de stratégie CORS dans ce document.

Les méthodes CorsPolicyBuilder peuvent être chaînées, comme indiqué dans le code suivant :

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Remarque : l’URL spécifiée ne doit pas contenir de barre oblique de fin (/). Si l’URL se termine par /, la comparaison retourne false et aucun en-tête n’est retourné.

Ordre des UseCors et des UseStaticFiles

En règle générale, UseStaticFiles est appelé avant UseCors. Les applications qui utilisent JavaScript pour récupérer des fichiers statiques entre sites doivent appeler UseCors avant UseStaticFiles.

CORS avec stratégie et intergiciel par défaut

Le code mis en surbrillance suivant active la stratégie CORS par défaut :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent applique la stratégie CORS par défaut à tous les points de terminaison du contrôleur.

Activer Cors avec le routage des points de terminaison

Avec le routage de point de terminaison, CORS peut être activé par point de terminaison à l’aide de l’ensemble RequireCors de méthodes d’extension :

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

Dans le code précédent :

  • app.UseCors active l’intergiciel CORS. Étant donné qu’aucune stratégie par défaut n’a été configurée, app.UseCors() elle seule n’active pas CORS.
  • Les points de terminaison du contrôleur et /echo autorisent les demandes inter-origines à l’aide de la stratégie spécifiée.
  • Les points de terminaison pages /echo2et Razor n’autorisent pas les demandes cross-origin, car aucune stratégie par défaut n’a été spécifiée.

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage de point de terminaison avec RequireCors.

Consultez Tester CORS avec l’attribut [EnableCors] et méthode RequireCors pour obtenir des instructions sur le code de test similaire au précédent.

Activer CORS avec des attributs

L’activation de CORS avec l’attribut [EnableCors] et l’application d’une stratégie nommée uniquement aux points de terminaison qui nécessitent CORS fournissent le meilleur contrôle.

L’attribut [EnableCors] fournit une alternative à l’application globale de CORS. L’attribut [EnableCors] active CORS pour les points de terminaison sélectionnés, plutôt que tous les points de terminaison :

  • [EnableCors] spécifie la stratégie par défaut.
  • [EnableCors("{Policy String}")] spécifie une stratégie nommée.

L’attribut [EnableCors] peut s'appliquer aux éléments suivants :

  • Razor Page PageModel
  • Contrôleur
  • Méthode d’action du contrôleur

Différentes stratégies peuvent être appliquées aux contrôleurs, aux modèles de page ou aux méthodes d’action avec l’attribut [EnableCors]. Lorsque l’attribut [EnableCors] est appliqué à un contrôleur, à un modèle de page ou à une méthode d’action, et que CORS est activé dans l’intergiciel, les deux stratégies sont appliquées. Nous vous déconseillons de combiner des stratégies. Utilisez le [EnableCors]attribut ou middleware, pas les deux dans la même application.

Le code suivant applique une stratégie différente à chaque méthode :

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Le code suivant crée deux stratégies CORS :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour le meilleur contrôle de la limitation des requêtes CORS :

  • Utilisez [EnableCors("MyPolicy")] avec une stratégie nommée.
  • Ne définissez pas de stratégie par défaut.
  • N’utilisez pas le routage de point de terminaison.

Le code de la section suivante répond à la liste précédente.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

Désactiver CORS

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage du point de terminaison.

Le code suivant définit la stratégie CORS "MyPolicy":

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints => {
    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Le code suivant désactive CORS pour l’action GetValues2 :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Le code précédent :

Pour obtenir des instructions sur le test du code précédent, consultez Test CORS.

Options de stratégie CORS

Cette section décrit les différentes options qui peuvent être définies dans une stratégie CORS :

AddPolicy est appelé dans Program.cs. Pour certaines options, il peut être utile de lire d’abord la section Fonctionnement de CORS.

Définir les origines autorisées

AllowAnyOrigin: autorise les requêtes CORS de toutes les origines avec n’importe quel schéma (http ou https). AllowAnyOrigin n’est pas sécurisé, car n’importe quel site web peut effectuer des demandes cross-origin à l’application.

Notes

La spécification de AllowAnyOrigin et AllowCredentials est une configuration non sécurisée et peut entraîner une falsification de requête intersites. Le service CORS retourne une réponse CORS non valide lorsqu’une application est configurée avec les deux méthodes.

AllowAnyOrigin affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Origin . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

SetIsOriginAllowedToAllowWildcardSubdomains: définit la propriété IsOriginAllowed de la stratégie comme une fonction qui permet aux origines de correspondre à un domaine générique configuré lors de l’évaluation de l’autorisation de l’origine.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Définir les méthodes HTTP autorisées

AllowAnyMethod:

  • Autorise n’importe quelle méthode HTTP :
  • Affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Methods. Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Définir les en-têtes de demande autorisés

Pour autoriser l’envoi d’en-têtes spécifiques dans une requête CORS, appelées en-têtes de demande d’auteur, appelez WithHeaders et spécifiez les en-têtes autorisés :

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader affecte les demandes préliminaires et l’en-tête Access-Control-Request-Headers . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Une politique CORS Middleware correspondant à des en-têtes spécifiques spécifiés par WithHeaders n'est possible que si les en-têtes envoyés dans Access-Control-Request-Headers correspondent exactement aux en-têtes indiqués dans WithHeaders.

Pour instance, envisagez une application configurée comme suit :

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware refuse une demande préliminaire avec l’en-tête de demande suivant, car Content-Language (HeaderNames.ContentLanguage) n’est pas répertorié dans WithHeaders :

Access-Control-Request-Headers: Cache-Control, Content-Language

L’application retourne une réponse 200 OK, mais ne renvoie pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin.

Définir les en-têtes de réponse exposés

Par défaut, le navigateur n’expose pas tous les en-têtes de réponse à l’application. Pour plus d’informations, consultez Partage de ressources cross-origin du W3C (terminologie) : en-tête de réponse simple.

Les en-têtes de réponse disponibles par défaut sont les suivants :

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La spécification CORS appelle ces en-têtes de réponse simples. Pour rendre d’autres en-têtes disponibles pour l’application, appelez WithExposedHeaders :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Informations d’identification dans les demandes cross-origin

Les informations d’identification nécessitent une gestion spéciale dans une demande CORS. Par défaut le navigateur n’envoie pas d’informations d’identification avec une requête de cross-origin. Les informations d’identification incluent des cookieschémas d’authentification s et HTTP. Pour envoyer des informations d’identification avec une requête cross-origin, le client doit définir XMLHttpRequest.withCredentials sur true.

En utilisant XMLHttpRequest directement :

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Utilisation de jQuery :

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Utilisation de l’API Fetch :

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Le serveur doit autoriser les informations d’identification. Pour autoriser les informations d’identification cross-origin, appelez AllowCredentials :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

La réponse HTTP inclut un Access-Control-Allow-Credentials en-tête, qui indique au navigateur que le serveur autorise les informations d’identification pour une requête cross-origin.

Si le navigateur envoie des informations d’identification mais que la réponse n’inclut pas d’en-tête valide Access-Control-Allow-Credentials, le navigateur n’expose pas la réponse à l’application et la demande cross-origin échoue.

L’autorisation des informations d’identification cross-origin est un risque pour la sécurité. Un site web d’un autre domaine peut envoyer les informations d’identification d’un utilisateur connecté à l’application pour le compte de l’utilisateur à l’insu de l’utilisateur.

La spécification CORS indique également que la définition des origines sur "*" (toutes les origines) n’est pas valide si l’en-tête Access-Control-Allow-Credentials est présent.

Demandes préliminaires

Pour certaines requêtes CORS, le navigateur envoie une requête OPTIONS supplémentaire avant d’effectuer la requête réelle. Cette requête est appelée demande préliminaire. Le navigateur peut ignorer la demande préliminaire si toutesles conditions suivantes sont remplies :

  • La méthode de demande est GET, HEAD ou POST.
  • L’application ne définit pas d’en-têtes de demande autres que Accept, Accept-Language, Content-Language, Content-Typeou Last-Event-ID.
  • L'en-tête Content-Type, s'il est défini, a l'une des valeurs suivantes :
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La règle sur les en-têtes de demande définis pour la demande cliente s’applique aux en-têtes définis par l’application en appelant setRequestHeader sur l’objet XMLHttpRequest. La spécification CORS appelle ces en-têtes des en-têtes de demande d’auteur. La règle ne s’applique pas aux en-têtes que le navigateur peut définir, tels que User-Agent, Hostou Content-Length.

Voici un exemple de réponse similaire à la demande préliminaire effectuée à partir du bouton [Put test] dans la section Test CORS de ce document.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La requête préliminaire utilise la méthode HTTP OPTIONS. Il peut inclure les en-têtes suivants :

Si la demande préliminaire est refusée, l’application retourne une réponse 200 OK, mais ne définit pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin. Pour obtenir un exemple de demande préliminaire refusée, consultez la section Test CORS de ce document.

À l’aide des outils F12, l’application console affiche une erreur similaire à l’une des suivantes, en fonction du navigateur :

  • Firefox : Requête cross-origin bloquée : la même stratégie d’origine interdit la lecture de la ressource distante à l’adresse https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motif : la demande CORS n’a pas réussi). En savoir plus
  • Basé sur Chromium : l’accès à l’extraction https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 à partir de l’origine https://cors3.azurewebsites.net a été bloqué par la stratégie CORS : la réponse à la demande préliminaire ne passe pas la vérification de contrôle d’accès : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Pour autoriser des en-têtes spécifiques, appelez WithHeaders :

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Les navigateurs ne sont pas cohérents dans la façon dont ils définissent Access-Control-Request-Headers. Si :

  • Les en-têtes sont définis sur autre chose que "*"
  • AllowAnyHeader est appelé : incluez au moins Accept, Content-Typeet Origin, ainsi que tous les en-têtes personnalisés que vous souhaitez prendre en charge.

Code de demande de contrôle préliminaire automatique

Lorsque la stratégie CORS est appliquée :

  • Globalement, en appelant app.UseCors dans Program.cs.
  • Utilisation de l’attribut [EnableCors].

ASP.NET Core répond à la demande d’OPTIONS en pré-vol.

La section Test CORS de ce document illustre ce comportement.

Attribut [HttpOptions] pour les demandes de contrôle préalable

Lorsque CORS est activé avec la stratégie appropriée, ASP.NET Core répond généralement automatiquement aux demandes de contrôle préalable CORS.

Le code suivant utilise l’attribut [HttpOptions] pour créer des points de terminaison pour les demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Consultez Tester CORS avec l’attribut [EnableCors] et méthode RequireCors pour obtenir des instructions sur le test de code précédent.

Définir l’heure d’expiration de la préversion

L’en-tête Access-Control-Max-Age spécifie la durée pendant laquelle la réponse à la demande de contrôle préliminaire peut être mise en cache. Pour définir cet en-tête, appelez SetPreflightMaxAge :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Activer CORS sur un point de terminaison

Fonctionnement de CORS

Cette section décrit ce qui se passe dans une requête CORS au niveau des messages HTTP.

  • CORS n’est pas une fonctionnalité de sécurité. CORS est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
    • Par exemple, un acteur malveillant peut utiliser des scripts intersite (XSS) sur votre site et exécuter une demande intersite à son site compatible CORS pour voler des informations.
  • Une API n’est pas plus sûre en autorisant CORS.
    • Il appartient au client (navigateur) d’appliquer CORS. Le serveur exécute la requête et retourne la réponse. C’est le client qui retourne une erreur et bloque la réponse. Par exemple, l’un des outils suivants affiche la réponse du serveur :
  • Il s’agit d’un moyen pour un serveur d’autoriser les navigateurs à exécuter une requête XHRouFetch API d’origine croisée qui, sinon, serait interdite.
    • Les navigateurs sans CORS ne peuvent pas effectuer de requêtes inter-origines. Avant CORS, JSONP était utilisé pour contourner cette restriction. JSONP n’utilise pas XHR, il utilise la balise <script> pour recevoir la réponse. Les scripts sont autorisés à être chargés entre origines.

La spécification CORS a introduit plusieurs nouveaux en-têtes HTTP qui activent les requêtes d’origine croisée. Si un navigateur prend en charge CORS, il définit automatiquement ces en-têtes pour les demandes d’origine croisée. Le code JavaScript personnalisé n’est pas nécessaire pour activer CORS.

Le Bouton de test PUT sur l’exemple déployé

Voici un exemple de demande d’origine croisée du bouton de test Valeurs vers https://cors1.azurewebsites.net/api/values. En-tête Origin :

  • Fournit le domaine du site qui effectue la demande.
  • Est obligatoire et doit être différent de l’hôte.

En-têtes généraux

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

En-têtes de réponse

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

Dans les requêtesOPTIONS, le serveur définit l’en-tête d’en-têtes de réponseAccess-Control-Allow-Origin: {allowed origin} dans la réponse. Par exemple, l’exemple déployé, le Supprimer [EnableCors]boutonOPTIONS contient les en-têtes suivants :

En-têtes généraux

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

En-têtes de réponse

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Dans les en-têtes de réponse précédents, le serveur définit l’en-tête Access-Control-Allow-Origin dans la réponse. La valeur https://cors1.azurewebsites.net de cet en-tête correspond à l’en-tête Origin de la demande.

Si AllowAnyOrigin est appelé,Access-Control-Allow-Origin: *, la valeur de caractère générique est retournée. AllowAnyOrigin autorise n’importe quelle origine.

Si la réponse n’inclut pas l’en-tête Access-Control-Allow-Origin, la demande d’origine croisée échoue. Plus précisément, le navigateur interdit la demande. Même si le serveur retourne une réponse réussie, le navigateur ne rend pas la réponse disponible pour l’application cliente.

La redirection HTTP vers HTTPS provoque des ERR_INVALID_REDIRECT sur la requête préliminaire CORS

Les demandes adressées à un point de terminaison à l’aide de HTTP qui sont redirigées vers HTTPS échouent UseHttpsRedirection avec ERR_INVALID_REDIRECT on the CORS preflight request.

Les projets d’API peuvent rejeter les requêtes HTTP au lieu de les utiliser UseHttpsRedirection pour rediriger les requêtes vers HTTPS.

CORS dans IIS

Lors du déploiement sur IIS, CORS doit s’exécuter avant l’authentification Windows si le serveur n’est pas configuré pour autoriser l’accès anonyme. Pour prendre en charge ce scénario, le module IIS CORS doit être installé et configuré pour l’application.

Tester CORS

L’exemple de téléchargement contient du code pour tester CORS. Consultez Guide pratique pour télécharger. L’exemple est un projet d’API avec Razor Pages ajoutées :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Avertissement

WithOrigins("https://localhost:<port>"); doit uniquement être utilisé pour tester un exemple d’application similaire à l’exemple de code de téléchargement.

ValuesController Voici les points de terminaison pour les tests :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

Testez l’exemple de code précédent à l’aide de l’une des approches suivantes :

  • Utilisez l’exemple d’application déployé sur https://cors3.azurewebsites.net/. Il n’est pas nécessaire de télécharger l’exemple.
  • Exécutez l’exemple avec dotnet run à l’aide de l’URL par défaut de https://localhost:5001.
  • Exécutez l’exemple à partir de Visual Studio avec le port défini sur 44398 pour une URL de https://localhost:44398.

Utilisation d’un navigateur avec les outils F12 :

  • Sélectionnez le bouton Valeurs et passez en revue les en-têtes sous l’onglet Réseau.

  • Sélectionnez le bouton test PUT. Consultez Afficher les demandes OPTIONS pour obtenir des instructions sur l’affichage de la demande OPTIONS. Le test PUT crée deux requêtes, une demande de contrôle préliminaire OPTIONS et la demande PUT.

  • Sélectionnez le bouton GetValues2 [DisableCors] pour déclencher une demande CORS ayant échoué. Comme indiqué dans le document, la réponse retourne 200 réussites, mais la demande CORS n’est pas effectuée. Sélectionnez l’onglet Console pour voir l’erreur CORS. Selon le navigateur, une erreur similaire à celle indiquée ci-après s'affiche :

    L’accès à l’extraction à 'https://cors1.azurewebsites.net/api/values/GetValues2' partir de l’origine 'https://cors3.azurewebsites.net' a été bloqué par la stratégie CORS : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Les points de terminaison compatibles CORS peuvent être testés avec un outil, tel que curl, Fiddler ou Postman. Lors de l’utilisation d’un outil, l’origine de la demande spécifiée par l’en-tête Origin doit différer de celle de l’hôte qui reçoit la demande. Si la requête n’est pas d’origine croisée basée sur la valeur de l’en-tête Origin :

  • Il n’est pas nécessaire d’utiliser l’intergiciel CORS pour traiter la demande.
  • Les en-têtes CORS ne sont pas retournés dans la réponse.

La commande suivante utilise curl pour émettre une demande OPTIONS avec des informations :

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Tester CORS avec l’attribut [EnableCors] et la méthode RequireCors

Considérez le code suivant qui utilise le routage de point de terminaison pour activer CORS par point de terminaison à l’aide de RequireCors :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors("MyPolicy");

    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Notez que seul le point de terminaison /echo utilise le RequireCors pour autoriser les demandes cross-origin à l’aide de la stratégie spécifiée. Les contrôleurs ci-dessous activent CORS à l’aide de l’attribut [EnableCors].

Les éléments suivants TodoItems1Controller fournissent des points de terminaison pour les tests :

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase 
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id) {
        if (id < 1) {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors("MyPolicy")]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé.

Les boutons Supprimer [EnableCors] et GET [EnableCors] réussissent, car les points de terminaison ont [EnableCors] et répondent aux demandes préliminaires. Les autres points de terminaison échouent. Le bouton GET échoue, car javaScript envoie :

 headers: {
      "Content-Type": "x-custom-header"
 },

Les éléments suivants TodoItems2Controller fournissent des points de terminaison similaires, mais incluent du code explicite pour répondre aux demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé. Dans la liste déroulante Contrôleur, sélectionnez Contrôle préliminaire, puis Définir le contrôleur. Tous les appels CORS aux points de terminaison TodoItems2Controller réussissent.

Ressources supplémentaires

Par Rick Anderson et Kirk Larkin

Cet article explique comment activer CORS dans une application ASP.NET Core.

La sécurité du navigateur empêche une page Web d’envoyer des requêtes à un domaine différent de celui qui a servi la page Web. Cette restriction est appelée stratégie de même origine. La politique de la même origine empêche un site malveillant de lire les données sensibles d'un autre site. Parfois, vous souhaiterez peut-être autoriser d’autres sites à effectuer des demandes cross-origin à votre application. Pour plus d’informations, consultez l’article Mozilla CORS.

CORS (Cross Origin Resource Sharing) :

  • C’est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
  • N’est pas une fonctionnalité de sécurité, CORS assouplit la sécurité. Une API n’est pas plus sûre en autorisant CORS. Pour plus d'informations, consultez la section Fonctionnement de CORS.
  • Permet à un serveur d’autoriser explicitement certaines demandes multi-origines tout en refusant d’autres.
  • Est plus sûr et plus flexible que les techniques précédentes telles que JSONP.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Même origine

Deux URL ont la même origine si elles ont des schémas, des hôtes et des ports (RFC 6454) identiques.

Ces deux URL ont la même origine :

  • https://example.com/foo.html
  • https://example.com/bar.html

Ces URL ont des origines différentes des deux URL précédentes :

  • https://example.net : Domaine différent
  • https://www.example.com/foo.html : Sous-domaine différent
  • http://example.com/foo.html : Schéma différent
  • https://example.com:9000/foo.html : Port différent

Activez CORS

Il y a trois manières pour activer CORS :

L’utilisation de l’attribut [EnableCors] avec une stratégie nommée fournit le meilleur contrôle pour limiter les points de terminaison qui prennent en charge CORS.

Avertissement

UseCors doit être appelé dans le bon ordre. Pour plus d’informations, consultez Ordre des intergiciels (middleware). Par exemple, UseCors doit être appelé avant UseResponseCaching lors de l’utilisation de UseResponseCaching.

Chaque approche est détaillée dans les sections suivantes.

CORS avec une stratégie et un intergiciel nommés

CORS Middleware gère les demandes cross-origin. Le code suivant applique une stratégie CORS à tous les points de terminaison de l’application avec les origines spécifiées :

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent :

Avec le routage de point de terminaison, l’intergiciel CORS doit être configuré pour s’exécuter entre les appels à UseRouting et UseEndpoints.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

L’appel de méthode AddCors ajoute des services CORS au conteneur de service de l’application :

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour plus d’informations, consultez Options de stratégie CORS dans ce document.

Les méthodes CorsPolicyBuilder peuvent être chaînées, comme indiqué dans le code suivant :

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Remarque : l’URL spécifiée ne doit pas contenir de barre oblique de fin (/). Si l’URL se termine par /, la comparaison retourne false et aucun en-tête n’est retourné.

Avertissement

UseCors doit être placé après UseRouting et avant UseAuthorization. Cela permet de s’assurer que les en-têtes CORS sont inclus dans la réponse pour les appels autorisés et non autorisés.

Ordre des UseCors et des UseStaticFiles

En règle générale, UseStaticFiles est appelé avant UseCors. Les applications qui utilisent JavaScript pour récupérer des fichiers statiques entre sites doivent appeler UseCors avant UseStaticFiles.

CORS avec stratégie et intergiciel par défaut

Le code mis en surbrillance suivant active la stratégie CORS par défaut :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent applique la stratégie CORS par défaut à tous les points de terminaison du contrôleur.

Activer Cors avec le routage des points de terminaison

Avec le routage de point de terminaison, CORS peut être activé par point de terminaison à l’aide de l’ensemble RequireCors de méthodes d’extension :

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

Dans le code précédent :

  • app.UseCors active l’intergiciel CORS. Étant donné qu’aucune stratégie par défaut n’a été configurée, app.UseCors() elle seule n’active pas CORS.
  • Les points de terminaison du contrôleur et /echo autorisent les demandes inter-origines à l’aide de la stratégie spécifiée.
  • Les points de terminaison pages /echo2et Razor n’autorisent pas les demandes cross-origin, car aucune stratégie par défaut n’a été spécifiée.

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage de point de terminaison avec RequireCors.

Dans ASP.NET Core 7.0, l’attribut [EnableCors] doit passer un paramètre ou un avertissement ASP0023 est généré à partir d’une correspondance ambiguë sur l’itinéraire. ASP.NET Core 8.0 et versions ultérieures ne génère pas l’avertissementASP0023.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Consultez Tester CORS avec l’attribut [EnableCors] et méthode RequireCors pour obtenir des instructions sur le code de test similaire au précédent.

Activer CORS avec des attributs

L’activation de CORS avec l’attribut [EnableCors] et l’application d’une stratégie nommée uniquement aux points de terminaison qui nécessitent CORS fournissent le meilleur contrôle.

L’attribut [EnableCors] fournit une alternative à l’application globale de CORS. L’attribut [EnableCors] active CORS pour les points de terminaison sélectionnés, plutôt que tous les points de terminaison :

  • [EnableCors] spécifie la stratégie par défaut.
  • [EnableCors("{Policy String}")] spécifie une stratégie nommée.

L’attribut [EnableCors] peut s'appliquer aux éléments suivants :

  • Razor Page PageModel
  • Contrôleur
  • Méthode d’action du contrôleur

Différentes stratégies peuvent être appliquées aux contrôleurs, aux modèles de page ou aux méthodes d’action avec l’attribut [EnableCors]. Lorsque l’attribut [EnableCors] est appliqué à un contrôleur, à un modèle de page ou à une méthode d’action, et que CORS est activé dans l’intergiciel, les deux stratégies sont appliquées. Nous vous déconseillons de combiner des stratégies. Utilisez le [EnableCors]attribut ou middleware, pas les deux dans la même application.

Le code suivant applique une stratégie différente à chaque méthode :

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Le code suivant crée deux stratégies CORS :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour le meilleur contrôle de la limitation des requêtes CORS :

  • Utilisez [EnableCors("MyPolicy")] avec une stratégie nommée.
  • Ne définissez pas de stratégie par défaut.
  • N’utilisez pas le routage de point de terminaison.

Le code de la section suivante répond à la liste précédente.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

Désactiver CORS

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage du point de terminaison.

Le code suivant définit la stratégie CORS "MyPolicy":

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints => {
    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Le code suivant désactive CORS pour l’action GetValues2 :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Le code précédent :

Pour obtenir des instructions sur le test du code précédent, consultez Test CORS.

Options de stratégie CORS

Cette section décrit les différentes options qui peuvent être définies dans une stratégie CORS :

AddPolicy est appelé dans Program.cs. Pour certaines options, il peut être utile de lire d’abord la section Fonctionnement de CORS.

Définir les origines autorisées

AllowAnyOrigin: autorise les requêtes CORS de toutes les origines avec n’importe quel schéma (http ou https). AllowAnyOrigin n’est pas sécurisé, car n’importe quel site web peut effectuer des demandes cross-origin à l’application.

Notes

La spécification de AllowAnyOrigin et AllowCredentials est une configuration non sécurisée et peut entraîner une falsification de requête intersites. Le service CORS retourne une réponse CORS non valide lorsqu’une application est configurée avec les deux méthodes.

AllowAnyOrigin affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Origin . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

SetIsOriginAllowedToAllowWildcardSubdomains: définit la propriété IsOriginAllowed de la stratégie comme une fonction qui permet aux origines de correspondre à un domaine générique configuré lors de l’évaluation de l’autorisation de l’origine.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Définir les méthodes HTTP autorisées

AllowAnyMethod :

  • Autorise n’importe quelle méthode HTTP :
  • Affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Methods. Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Définir les en-têtes de demande autorisés

Pour autoriser l’envoi d’en-têtes spécifiques dans une requête CORS, appelées en-têtes de demande d’auteur, appelez WithHeaders et spécifiez les en-têtes autorisés :

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader affecte les demandes préliminaires et l’en-tête Access-Control-Request-Headers . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Une politique CORS Middleware correspondant à des en-têtes spécifiques spécifiés par WithHeaders n'est possible que si les en-têtes envoyés dans Access-Control-Request-Headers correspondent exactement aux en-têtes indiqués dans WithHeaders.

Pour instance, envisagez une application configurée comme suit :

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware refuse une demande préliminaire avec l’en-tête de demande suivant, car Content-Language (HeaderNames.ContentLanguage) n’est pas répertorié dans WithHeaders :

Access-Control-Request-Headers: Cache-Control, Content-Language

L’application retourne une réponse 200 OK, mais ne renvoie pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin.

Définir les en-têtes de réponse exposés

Par défaut, le navigateur n’expose pas tous les en-têtes de réponse à l’application. Pour plus d’informations, consultez Partage de ressources cross-origin du W3C (terminologie) : en-tête de réponse simple.

Les en-têtes de réponse disponibles par défaut sont les suivants :

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La spécification CORS appelle ces en-têtes de réponse simples. Pour rendre d’autres en-têtes disponibles pour l’application, appelez WithExposedHeaders :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Informations d’identification dans les demandes cross-origin

Les informations d’identification nécessitent une gestion spéciale dans une demande CORS. Par défaut le navigateur n’envoie pas d’informations d’identification avec une requête de cross-origin. Les informations d’identification incluent des cookieschémas d’authentification s et HTTP. Pour envoyer des informations d’identification avec une requête cross-origin, le client doit définir XMLHttpRequest.withCredentials sur true.

En utilisant XMLHttpRequest directement :

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Utilisation de jQuery :

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Utilisation de l’API Fetch :

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Le serveur doit autoriser les informations d’identification. Pour autoriser les informations d’identification cross-origin, appelez AllowCredentials :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

La réponse HTTP inclut un Access-Control-Allow-Credentials en-tête, qui indique au navigateur que le serveur autorise les informations d’identification pour une requête cross-origin.

Si le navigateur envoie des informations d’identification mais que la réponse n’inclut pas d’en-tête valide Access-Control-Allow-Credentials, le navigateur n’expose pas la réponse à l’application et la demande cross-origin échoue.

L’autorisation des informations d’identification cross-origin est un risque pour la sécurité. Un site web d’un autre domaine peut envoyer les informations d’identification d’un utilisateur connecté à l’application pour le compte de l’utilisateur à l’insu de l’utilisateur.

La spécification CORS indique également que la définition des origines sur "*" (toutes les origines) n’est pas valide si l’en-tête Access-Control-Allow-Credentials est présent.

Demandes préliminaires

Pour certaines requêtes CORS, le navigateur envoie une requête OPTIONS supplémentaire avant d’effectuer la requête réelle. Cette requête est appelée demande préliminaire. Le navigateur peut ignorer la demande préliminaire si toutesles conditions suivantes sont remplies :

  • La méthode de demande est GET, HEAD ou POST.
  • L’application ne définit pas d’en-têtes de demande autres que Accept, Accept-Language, Content-Language, Content-Typeou Last-Event-ID.
  • L'en-tête Content-Type, s'il est défini, a l'une des valeurs suivantes :
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La règle sur les en-têtes de demande définis pour la demande cliente s’applique aux en-têtes définis par l’application en appelant setRequestHeader sur l’objet XMLHttpRequest. La spécification CORS appelle ces en-têtes des en-têtes de demande d’auteur. La règle ne s’applique pas aux en-têtes que le navigateur peut définir, tels que User-Agent, Hostou Content-Length.

Voici un exemple de réponse similaire à la demande préliminaire effectuée à partir du bouton [Put test] dans la section Test CORS de ce document.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La requête préliminaire utilise la méthode HTTP OPTIONS. Il peut inclure les en-têtes suivants :

Si la demande préliminaire est refusée, l’application retourne une réponse 200 OK, mais ne définit pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin. Pour obtenir un exemple de demande préliminaire refusée, consultez la section Test CORS de ce document.

À l’aide des outils F12, l’application console affiche une erreur similaire à l’une des suivantes, en fonction du navigateur :

  • Firefox : Requête cross-origin bloquée : la même stratégie d’origine interdit la lecture de la ressource distante à l’adresse https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motif : la demande CORS n’a pas réussi). En savoir plus
  • Basé sur Chromium : l’accès à l’extraction https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 à partir de l’origine https://cors3.azurewebsites.net a été bloqué par la stratégie CORS : la réponse à la demande préliminaire ne passe pas la vérification de contrôle d’accès : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Pour autoriser des en-têtes spécifiques, appelez WithHeaders :

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Les navigateurs ne sont pas cohérents dans la façon dont ils définissent Access-Control-Request-Headers. Si :

  • Les en-têtes sont définis sur autre chose que "*"
  • AllowAnyHeader est appelé : incluez au moins Accept, Content-Typeet Origin, ainsi que tous les en-têtes personnalisés que vous souhaitez prendre en charge.

Code de demande de contrôle préliminaire automatique

Lorsque la stratégie CORS est appliquée :

  • Globalement, en appelant app.UseCors dans Program.cs.
  • Utilisation de l’attribut [EnableCors].

ASP.NET Core répond à la demande d’OPTIONS en pré-vol.

La section Test CORS de ce document illustre ce comportement.

Attribut [HttpOptions] pour les demandes de contrôle préalable

Lorsque CORS est activé avec la stratégie appropriée, ASP.NET Core répond généralement automatiquement aux demandes de contrôle préalable CORS.

Le code suivant utilise l’attribut [HttpOptions] pour créer des points de terminaison pour les demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Consultez Tester CORS avec l’attribut [EnableCors] et méthode RequireCors pour obtenir des instructions sur le test de code précédent.

Définir l’heure d’expiration de la préversion

L’en-tête Access-Control-Max-Age spécifie la durée pendant laquelle la réponse à la demande de contrôle préliminaire peut être mise en cache. Pour définir cet en-tête, appelez SetPreflightMaxAge :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Activer CORS sur un point de terminaison

Fonctionnement de CORS

Cette section décrit ce qui se passe dans une requête CORS au niveau des messages HTTP.

  • CORS n’est pas une fonctionnalité de sécurité. CORS est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
    • Par exemple, un acteur malveillant peut utiliser des scripts intersite (XSS) sur votre site et exécuter une demande intersite à son site compatible CORS pour voler des informations.
  • Une API n’est pas plus sûre en autorisant CORS.
    • Il appartient au client (navigateur) d’appliquer CORS. Le serveur exécute la requête et retourne la réponse. C’est le client qui retourne une erreur et bloque la réponse. Par exemple, l’un des outils suivants affiche la réponse du serveur :
  • Il s’agit d’un moyen pour un serveur d’autoriser les navigateurs à exécuter une requête XHRouFetch API d’origine croisée qui, sinon, serait interdite.
    • Les navigateurs sans CORS ne peuvent pas effectuer de requêtes inter-origines. Avant CORS, JSONP était utilisé pour contourner cette restriction. JSONP n’utilise pas XHR, il utilise la balise <script> pour recevoir la réponse. Les scripts sont autorisés à être chargés entre origines.

La spécification CORS a introduit plusieurs nouveaux en-têtes HTTP qui activent les requêtes d’origine croisée. Si un navigateur prend en charge CORS, il définit automatiquement ces en-têtes pour les demandes d’origine croisée. Le code JavaScript personnalisé n’est pas nécessaire pour activer CORS.

Le Bouton de test PUT sur l’exemple déployé

Voici un exemple de demande d’origine croisée du bouton de test Valeurs vers https://cors1.azurewebsites.net/api/values. En-tête Origin :

  • Fournit le domaine du site qui effectue la demande.
  • Est obligatoire et doit être différent de l’hôte.

En-têtes généraux

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

En-têtes de réponse

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

Dans les requêtesOPTIONS, le serveur définit l’en-tête d’en-têtes de réponseAccess-Control-Allow-Origin: {allowed origin} dans la réponse. Par exemple, l’exemple déployé, le Supprimer [EnableCors]boutonOPTIONS contient les en-têtes suivants :

En-têtes généraux

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

En-têtes de réponse

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Dans les en-têtes de réponse précédents, le serveur définit l’en-tête Access-Control-Allow-Origin dans la réponse. La valeur https://cors1.azurewebsites.net de cet en-tête correspond à l’en-tête Origin de la demande.

Si AllowAnyOrigin est appelé,Access-Control-Allow-Origin: *, la valeur de caractère générique est retournée. AllowAnyOrigin autorise n’importe quelle origine.

Si la réponse n’inclut pas l’en-tête Access-Control-Allow-Origin, la demande d’origine croisée échoue. Plus précisément, le navigateur interdit la demande. Même si le serveur retourne une réponse réussie, le navigateur ne rend pas la réponse disponible pour l’application cliente.

La redirection HTTP vers HTTPS provoque des ERR_INVALID_REDIRECT sur la requête préliminaire CORS

Les demandes adressées à un point de terminaison à l’aide de HTTP qui sont redirigées vers HTTPS échouent UseHttpsRedirection avec ERR_INVALID_REDIRECT on the CORS preflight request.

Les projets d’API peuvent rejeter les requêtes HTTP au lieu de les utiliser UseHttpsRedirection pour rediriger les requêtes vers HTTPS.

CORS dans IIS

Lors du déploiement sur IIS, CORS doit s’exécuter avant l’authentification Windows si le serveur n’est pas configuré pour autoriser l’accès anonyme. Pour prendre en charge ce scénario, le module IIS CORS doit être installé et configuré pour l’application.

Tester CORS

L’exemple de téléchargement contient du code pour tester CORS. Consultez Guide pratique pour télécharger. L’exemple est un projet d’API avec Razor Pages ajoutées :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Avertissement

WithOrigins("https://localhost:<port>"); doit uniquement être utilisé pour tester un exemple d’application similaire à l’exemple de code de téléchargement.

ValuesController Voici les points de terminaison pour les tests :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

Testez l’exemple de code précédent à l’aide de l’une des approches suivantes :

  • Utilisez l’exemple d’application déployé sur https://cors3.azurewebsites.net/. Il n’est pas nécessaire de télécharger l’exemple.
  • Exécutez l’exemple avec dotnet run à l’aide de l’URL par défaut de https://localhost:5001.
  • Exécutez l’exemple à partir de Visual Studio avec le port défini sur 44398 pour une URL de https://localhost:44398.

Utilisation d’un navigateur avec les outils F12 :

  • Sélectionnez le bouton Valeurs et passez en revue les en-têtes sous l’onglet Réseau.

  • Sélectionnez le bouton test PUT. Consultez Afficher les demandes OPTIONS pour obtenir des instructions sur l’affichage de la demande OPTIONS. Le test PUT crée deux requêtes, une demande de contrôle préliminaire OPTIONS et la demande PUT.

  • Sélectionnez le bouton GetValues2 [DisableCors] pour déclencher une demande CORS ayant échoué. Comme indiqué dans le document, la réponse retourne 200 réussites, mais la demande CORS n’est pas effectuée. Sélectionnez l’onglet Console pour voir l’erreur CORS. Selon le navigateur, une erreur similaire à celle indiquée ci-après s'affiche :

    L’accès à l’extraction à 'https://cors1.azurewebsites.net/api/values/GetValues2' partir de l’origine 'https://cors3.azurewebsites.net' a été bloqué par la stratégie CORS : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Les points de terminaison compatibles CORS peuvent être testés avec un outil, tel que curl, Fiddler ou Postman. Lors de l’utilisation d’un outil, l’origine de la demande spécifiée par l’en-tête Origin doit différer de celle de l’hôte qui reçoit la demande. Si la requête n’est pas d’origine croisée basée sur la valeur de l’en-tête Origin :

  • Il n’est pas nécessaire d’utiliser l’intergiciel CORS pour traiter la demande.
  • Les en-têtes CORS ne sont pas retournés dans la réponse.

La commande suivante utilise curl pour émettre une demande OPTIONS avec des informations :

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Tester CORS avec l’attribut [EnableCors] et la méthode RequireCors

Considérez le code suivant qui utilise le routage de point de terminaison pour activer CORS par point de terminaison à l’aide de RequireCors :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors("MyPolicy");

    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Notez que seul le point de terminaison /echo utilise le RequireCors pour autoriser les demandes cross-origin à l’aide de la stratégie spécifiée. Les contrôleurs ci-dessous activent CORS à l’aide de l’attribut [EnableCors].

Les éléments suivants TodoItems1Controller fournissent des points de terminaison pour les tests :

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase 
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id) {
        if (id < 1) {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors("MyPolicy")]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé.

Les boutons Supprimer [EnableCors] et GET [EnableCors] réussissent, car les points de terminaison ont [EnableCors] et répondent aux demandes préliminaires. Les autres points de terminaison échouent. Le bouton GET échoue, car javaScript envoie :

 headers: {
      "Content-Type": "x-custom-header"
 },

Les éléments suivants TodoItems2Controller fournissent des points de terminaison similaires, mais incluent du code explicite pour répondre aux demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé. Dans la liste déroulante Contrôleur, sélectionnez Contrôle préliminaire, puis Définir le contrôleur. Tous les appels CORS aux points de terminaison TodoItems2Controller réussissent.

Ressources supplémentaires

Par Rick Anderson et Kirk Larkin

Cet article explique comment activer CORS dans une application ASP.NET Core.

La sécurité du navigateur empêche une page Web d’envoyer des requêtes à un domaine différent de celui qui a servi la page Web. Cette restriction est appelée stratégie de même origine. La politique de la même origine empêche un site malveillant de lire les données sensibles d'un autre site. Parfois, vous souhaiterez peut-être autoriser d’autres sites à effectuer des demandes cross-origin à votre application. Pour plus d’informations, consultez l’article Mozilla CORS.

CORS (Cross Origin Resource Sharing) :

  • C’est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
  • N’est pas une fonctionnalité de sécurité, CORS assouplit la sécurité. Une API n’est pas plus sûre en autorisant CORS. Pour plus d'informations, consultez la section Fonctionnement de CORS.
  • Permet à un serveur d’autoriser explicitement certaines demandes multi-origines tout en refusant d’autres.
  • Est plus sûr et plus flexible que les techniques précédentes telles que JSONP.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Même origine

Deux URL ont la même origine si elles ont des schémas, des hôtes et des ports (RFC 6454) identiques.

Ces deux URL ont la même origine :

  • https://example.com/foo.html
  • https://example.com/bar.html

Ces URL ont des origines différentes des deux URL précédentes :

  • https://example.net : Domaine différent
  • https://www.example.com/foo.html : Sous-domaine différent
  • http://example.com/foo.html : Schéma différent
  • https://example.com:9000/foo.html : Port différent

Activez CORS

Il y a trois manières pour activer CORS :

L’utilisation de l’attribut [EnableCors] avec une stratégie nommée fournit le meilleur contrôle pour limiter les points de terminaison qui prennent en charge CORS.

Avertissement

UseCors doit être appelé dans le bon ordre. Pour plus d’informations, consultez Ordre des intergiciels (middleware). Par exemple, UseCors doit être appelé avant UseResponseCaching lors de l’utilisation de UseResponseCaching.

Chaque approche est détaillée dans les sections suivantes.

CORS avec une stratégie et un intergiciel nommés

CORS Middleware gère les demandes cross-origin. Le code suivant applique une stratégie CORS à tous les points de terminaison de l’application avec les origines spécifiées :

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent :

Avec le routage de point de terminaison, l’intergiciel CORS doit être configuré pour s’exécuter entre les appels à UseRouting et UseEndpoints.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

L’appel de méthode AddCors ajoute des services CORS au conteneur de service de l’application :

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour plus d’informations, consultez Options de stratégie CORS dans ce document.

Les méthodes CorsPolicyBuilder peuvent être chaînées, comme indiqué dans le code suivant :

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Remarque : l’URL spécifiée ne doit pas contenir de barre oblique de fin (/). Si l’URL se termine par /, la comparaison retourne false et aucun en-tête n’est retourné.

Avertissement

UseCors doit être placé après UseRouting et avant UseAuthorization. Cela permet de s’assurer que les en-têtes CORS sont inclus dans la réponse pour les appels autorisés et non autorisés.

Ordre des UseCors et des UseStaticFiles

En règle générale, UseStaticFiles est appelé avant UseCors. Les applications qui utilisent JavaScript pour récupérer des fichiers statiques entre sites doivent appeler UseCors avant UseStaticFiles.

CORS avec stratégie et intergiciel par défaut

Le code mis en surbrillance suivant active la stratégie CORS par défaut :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent applique la stratégie CORS par défaut à tous les points de terminaison du contrôleur.

Activer Cors avec le routage des points de terminaison

L’activation de CORS par point de terminaison à l’aide RequireCorsne prend pas en charge les demandes préliminaires automatiques. Pour plus d’informations, consultez ce problème GitHub et Test CORS avec routage de point de terminaison et [HttpOptions].

Avec le routage de point de terminaison, CORS peut être activé par point de terminaison à l’aide de l’ensemble RequireCors de méthodes d’extension :

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

Dans le code précédent :

  • app.UseCors active l’intergiciel CORS. Étant donné qu’aucune stratégie par défaut n’a été configurée, app.UseCors() elle seule n’active pas CORS.
  • Les points de terminaison du contrôleur et /echo autorisent les demandes inter-origines à l’aide de la stratégie spécifiée.
  • Les points de terminaison pages /echo2et Razor n’autorisent pas les demandes cross-origin, car aucune stratégie par défaut n’a été spécifiée.

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage de point de terminaison avec RequireCors.

Pour obtenir des instructions sur le test du code similaire au précédent, consultez Test CORS avec routage de point de terminaison et [HttpOptions].

Activer CORS avec des attributs

L’activation de CORS avec l’attribut [EnableCors] et l’application d’une stratégie nommée uniquement aux points de terminaison qui nécessitent CORS fournissent le meilleur contrôle.

L’attribut [EnableCors] fournit une alternative à l’application globale de CORS. L’attribut [EnableCors] active CORS pour les points de terminaison sélectionnés, plutôt que tous les points de terminaison :

  • [EnableCors] spécifie la stratégie par défaut.
  • [EnableCors("{Policy String}")] spécifie une stratégie nommée.

L’attribut [EnableCors] peut s'appliquer aux éléments suivants :

  • Razor Page PageModel
  • Contrôleur
  • Méthode d’action du contrôleur

Différentes stratégies peuvent être appliquées aux contrôleurs, aux modèles de page ou aux méthodes d’action avec l’attribut [EnableCors]. Lorsque l’attribut [EnableCors] est appliqué à un contrôleur, à un modèle de page ou à une méthode d’action, et que CORS est activé dans l’intergiciel, les deux stratégies sont appliquées. Nous vous déconseillons de combiner des stratégies. Utilisez le [EnableCors]attribut ou middleware, pas les deux dans la même application.

Le code suivant applique une stratégie différente à chaque méthode :

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Le code suivant crée deux stratégies CORS :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour le meilleur contrôle de la limitation des requêtes CORS :

  • Utilisez [EnableCors("MyPolicy")] avec une stratégie nommée.
  • Ne définissez pas de stratégie par défaut.
  • N’utilisez pas le routage de point de terminaison.

Le code de la section suivante répond à la liste précédente.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

Désactiver CORS

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage du point de terminaison.

Le code suivant définit la stratégie CORS "MyPolicy":

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Le code suivant désactive CORS pour l’action GetValues2 :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Le code précédent :

Pour obtenir des instructions sur le test du code précédent, consultez Test CORS.

Options de stratégie CORS

Cette section décrit les différentes options qui peuvent être définies dans une stratégie CORS :

AddPolicy est appelé dans Program.cs. Pour certaines options, il peut être utile de lire d’abord la section Fonctionnement de CORS.

Définir les origines autorisées

AllowAnyOrigin: autorise les requêtes CORS de toutes les origines avec n’importe quel schéma (http ou https). AllowAnyOrigin n’est pas sécurisé, car n’importe quel site web peut effectuer des demandes cross-origin à l’application.

Notes

La spécification de AllowAnyOrigin et AllowCredentials est une configuration non sécurisée et peut entraîner une falsification de requête intersites. Le service CORS retourne une réponse CORS non valide lorsqu’une application est configurée avec les deux méthodes.

AllowAnyOrigin affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Origin . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

SetIsOriginAllowedToAllowWildcardSubdomains: définit la propriété IsOriginAllowed de la stratégie comme une fonction qui permet aux origines de correspondre à un domaine générique configuré lors de l’évaluation de l’autorisation de l’origine.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Définir les méthodes HTTP autorisées

AllowAnyMethod :

  • Autorise n’importe quelle méthode HTTP :
  • Affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Methods. Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Définir les en-têtes de demande autorisés

Pour autoriser l’envoi d’en-têtes spécifiques dans une requête CORS, appelées en-têtes de demande d’auteur, appelez WithHeaders et spécifiez les en-têtes autorisés :

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader affecte les demandes préliminaires et l’en-tête Access-Control-Request-Headers . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Une politique CORS Middleware correspondant à des en-têtes spécifiques spécifiés par WithHeaders n'est possible que si les en-têtes envoyés dans Access-Control-Request-Headers correspondent exactement aux en-têtes indiqués dans WithHeaders.

Pour instance, envisagez une application configurée comme suit :

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware refuse une demande préliminaire avec l’en-tête de demande suivant, car Content-Language (HeaderNames.ContentLanguage) n’est pas répertorié dans WithHeaders :

Access-Control-Request-Headers: Cache-Control, Content-Language

L’application retourne une réponse 200 OK, mais ne renvoie pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin.

Définir les en-têtes de réponse exposés

Par défaut, le navigateur n’expose pas tous les en-têtes de réponse à l’application. Pour plus d’informations, consultez Partage de ressources cross-origin du W3C (terminologie) : en-tête de réponse simple.

Les en-têtes de réponse disponibles par défaut sont les suivants :

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La spécification CORS appelle ces en-têtes de réponse simples. Pour rendre d’autres en-têtes disponibles pour l’application, appelez WithExposedHeaders :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Informations d’identification dans les demandes cross-origin

Les informations d’identification nécessitent une gestion spéciale dans une demande CORS. Par défaut le navigateur n’envoie pas d’informations d’identification avec une requête de cross-origin. Les informations d’identification incluent des cookieschémas d’authentification s et HTTP. Pour envoyer des informations d’identification avec une requête cross-origin, le client doit définir XMLHttpRequest.withCredentials sur true.

En utilisant XMLHttpRequest directement :

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Utilisation de jQuery :

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Utilisation de l’API Fetch :

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Le serveur doit autoriser les informations d’identification. Pour autoriser les informations d’identification cross-origin, appelez AllowCredentials :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

La réponse HTTP inclut un Access-Control-Allow-Credentials en-tête, qui indique au navigateur que le serveur autorise les informations d’identification pour une requête cross-origin.

Si le navigateur envoie des informations d’identification mais que la réponse n’inclut pas d’en-tête valide Access-Control-Allow-Credentials, le navigateur n’expose pas la réponse à l’application et la demande cross-origin échoue.

L’autorisation des informations d’identification cross-origin est un risque pour la sécurité. Un site web d’un autre domaine peut envoyer les informations d’identification d’un utilisateur connecté à l’application pour le compte de l’utilisateur à l’insu de l’utilisateur.

La spécification CORS indique également que la définition des origines sur "*" (toutes les origines) n’est pas valide si l’en-tête Access-Control-Allow-Credentials est présent.

Demandes préliminaires

Pour certaines requêtes CORS, le navigateur envoie une requête OPTIONS supplémentaire avant d’effectuer la requête réelle. Cette requête est appelée demande préliminaire. Le navigateur peut ignorer la demande préliminaire si toutesles conditions suivantes sont remplies :

  • La méthode de demande est GET, HEAD ou POST.
  • L’application ne définit pas d’en-têtes de demande autres que Accept, Accept-Language, Content-Language, Content-Typeou Last-Event-ID.
  • L'en-tête Content-Type, s'il est défini, a l'une des valeurs suivantes :
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La règle sur les en-têtes de demande définis pour la demande cliente s’applique aux en-têtes définis par l’application en appelant setRequestHeader sur l’objet XMLHttpRequest. La spécification CORS appelle ces en-têtes des en-têtes de demande d’auteur. La règle ne s’applique pas aux en-têtes que le navigateur peut définir, tels que User-Agent, Hostou Content-Length.

Voici un exemple de réponse similaire à la demande préliminaire effectuée à partir du bouton [Put test] dans la section Test CORS de ce document.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La requête préliminaire utilise la méthode HTTP OPTIONS. Il peut inclure les en-têtes suivants :

Si la demande préliminaire est refusée, l’application retourne une réponse 200 OK, mais ne définit pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin. Pour obtenir un exemple de demande préliminaire refusée, consultez la section Test CORS de ce document.

À l’aide des outils F12, l’application console affiche une erreur similaire à l’une des suivantes, en fonction du navigateur :

  • Firefox : Requête cross-origin bloquée : la même stratégie d’origine interdit la lecture de la ressource distante à l’adresse https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motif : la demande CORS n’a pas réussi). En savoir plus
  • Basé sur Chromium : l’accès à l’extraction https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 à partir de l’origine https://cors3.azurewebsites.net a été bloqué par la stratégie CORS : la réponse à la demande préliminaire ne passe pas la vérification de contrôle d’accès : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Pour autoriser des en-têtes spécifiques, appelez WithHeaders :

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Les navigateurs ne sont pas cohérents dans la façon dont ils définissent Access-Control-Request-Headers. Si :

  • Les en-têtes sont définis sur autre chose que "*"
  • AllowAnyHeader est appelé : incluez au moins Accept, Content-Typeet Origin, ainsi que tous les en-têtes personnalisés que vous souhaitez prendre en charge.

Code de demande de contrôle préliminaire automatique

Lorsque la stratégie CORS est appliquée :

  • Globalement, en appelant app.UseCors dans Program.cs.
  • Utilisation de l’attribut [EnableCors].

ASP.NET Core répond à la demande d’OPTIONS en pré-vol.

L’activation de CORS sur une base par point de terminaison à l’aide de RequireCors ne prend actuellement pas en charge les demandes de contrôle préliminaire automatique.

La section Test CORS de ce document illustre ce comportement.

Attribut [HttpOptions] pour les demandes de contrôle préalable

Lorsque CORS est activé avec la stratégie appropriée, ASP.NET Core répond généralement automatiquement aux demandes de contrôle préalable CORS. Dans certains scénarios, cela peut ne pas être le cas. Par exemple, l’utilisation de CORS avec le routage de point de terminaison.

Le code suivant utilise l’attribut [HttpOptions] pour créer des points de terminaison pour les demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Pour obtenir des instructions sur le test du code précédent, consultez Test CORS avec routage de point de terminaison et [HttpOptions ].

Définir l’heure d’expiration de la préversion

L’en-tête Access-Control-Max-Age spécifie la durée pendant laquelle la réponse à la demande de contrôle préliminaire peut être mise en cache. Pour définir cet en-tête, appelez SetPreflightMaxAge :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Fonctionnement de CORS

Cette section décrit ce qui se passe dans une requête CORS au niveau des messages HTTP.

  • CORS n’est pas une fonctionnalité de sécurité. CORS est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
    • Par exemple, un acteur malveillant peut utiliser des scripts intersite (XSS) sur votre site et exécuter une demande intersite à son site compatible CORS pour voler des informations.
  • Une API n’est pas plus sûre en autorisant CORS.
    • Il appartient au client (navigateur) d’appliquer CORS. Le serveur exécute la requête et retourne la réponse. C’est le client qui retourne une erreur et bloque la réponse. Par exemple, l’un des outils suivants affiche la réponse du serveur :
  • Il s’agit d’un moyen pour un serveur d’autoriser les navigateurs à exécuter une requête XHRouFetch API d’origine croisée qui, sinon, serait interdite.
    • Les navigateurs sans CORS ne peuvent pas effectuer de requêtes inter-origines. Avant CORS, JSONP était utilisé pour contourner cette restriction. JSONP n’utilise pas XHR, il utilise la balise <script> pour recevoir la réponse. Les scripts sont autorisés à être chargés entre origines.

La spécification CORS a introduit plusieurs nouveaux en-têtes HTTP qui activent les requêtes d’origine croisée. Si un navigateur prend en charge CORS, il définit automatiquement ces en-têtes pour les demandes d’origine croisée. Le code JavaScript personnalisé n’est pas nécessaire pour activer CORS.

Le Bouton de test PUT sur l’exemple déployé

Voici un exemple de demande d’origine croisée du bouton de test Valeurs vers https://cors1.azurewebsites.net/api/values. En-tête Origin :

  • Fournit le domaine du site qui effectue la demande.
  • Est obligatoire et doit être différent de l’hôte.

En-têtes généraux

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

En-têtes de réponse

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

Dans les requêtesOPTIONS, le serveur définit l’en-tête d’en-têtes de réponseAccess-Control-Allow-Origin: {allowed origin} dans la réponse. Par exemple, l’exemple déployé, le Supprimer [EnableCors]boutonOPTIONS contient les en-têtes suivants :

En-têtes généraux

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

En-têtes de réponse

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Dans les en-têtes de réponse précédents, le serveur définit l’en-tête Access-Control-Allow-Origin dans la réponse. La valeur https://cors1.azurewebsites.net de cet en-tête correspond à l’en-tête Origin de la demande.

Si AllowAnyOrigin est appelé,Access-Control-Allow-Origin: *, la valeur de caractère générique est retournée. AllowAnyOrigin autorise n’importe quelle origine.

Si la réponse n’inclut pas l’en-tête Access-Control-Allow-Origin, la demande d’origine croisée échoue. Plus précisément, le navigateur interdit la demande. Même si le serveur retourne une réponse réussie, le navigateur ne rend pas la réponse disponible pour l’application cliente.

La redirection HTTP vers HTTPS provoque des ERR_INVALID_REDIRECT sur la requête préliminaire CORS

Les demandes adressées à un point de terminaison à l’aide de HTTP qui sont redirigées vers HTTPS échouent UseHttpsRedirection avec ERR_INVALID_REDIRECT on the CORS preflight request.

Les projets d’API peuvent rejeter les requêtes HTTP au lieu de les utiliser UseHttpsRedirection pour rediriger les requêtes vers HTTPS.

Afficher les demandes OPTIONS

Par défaut, les navigateurs Chrome et Edge n’affichent pas les requêtes OPTIONS sous l’onglet réseau des outils F12. Pour afficher les demandes OPTIONS dans ces navigateurs :

  • chrome://flags/#out-of-blink-cors ou edge://flags/#out-of-blink-cors
  • désactivez l’indicateur.
  • redémarrer.

Firefox affiche les demandes OPTIONS par défaut.

CORS dans IIS

Lors du déploiement sur IIS, CORS doit s’exécuter avant l’authentification Windows si le serveur n’est pas configuré pour autoriser l’accès anonyme. Pour prendre en charge ce scénario, le module IIS CORS doit être installé et configuré pour l’application.

Tester CORS

L’exemple de téléchargement contient du code pour tester CORS. Consultez Guide pratique pour télécharger. L’exemple est un projet d’API avec Razor Pages ajoutées :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Avertissement

WithOrigins("https://localhost:<port>"); doit uniquement être utilisé pour tester un exemple d’application similaire à l’exemple de code de téléchargement.

ValuesController Voici les points de terminaison pour les tests :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

Testez l’exemple de code précédent à l’aide de l’une des approches suivantes :

  • Utilisez l’exemple d’application déployé sur https://cors3.azurewebsites.net/. Il n’est pas nécessaire de télécharger l’exemple.
  • Exécutez l’exemple avec dotnet run à l’aide de l’URL par défaut de https://localhost:5001.
  • Exécutez l’exemple à partir de Visual Studio avec le port défini sur 44398 pour une URL de https://localhost:44398.

Utilisation d’un navigateur avec les outils F12 :

  • Sélectionnez le bouton Valeurs et passez en revue les en-têtes sous l’onglet Réseau.

  • Sélectionnez le bouton test PUT. Consultez Afficher les demandes OPTIONS pour obtenir des instructions sur l’affichage de la demande OPTIONS. Le test PUT crée deux requêtes, une demande de contrôle préliminaire OPTIONS et la demande PUT.

  • Sélectionnez le bouton GetValues2 [DisableCors] pour déclencher une demande CORS ayant échoué. Comme indiqué dans le document, la réponse retourne 200 réussites, mais la demande CORS n’est pas effectuée. Sélectionnez l’onglet Console pour voir l’erreur CORS. Selon le navigateur, une erreur similaire à celle indiquée ci-après s'affiche :

    L’accès à l’extraction à 'https://cors1.azurewebsites.net/api/values/GetValues2' partir de l’origine 'https://cors3.azurewebsites.net' a été bloqué par la stratégie CORS : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Les points de terminaison compatibles CORS peuvent être testés avec un outil, tel que curl, Fiddler ou Postman. Lors de l’utilisation d’un outil, l’origine de la demande spécifiée par l’en-tête Origin doit différer de celle de l’hôte qui reçoit la demande. Si la requête n’est pas d’origine croisée basée sur la valeur de l’en-tête Origin :

  • Il n’est pas nécessaire d’utiliser l’intergiciel CORS pour traiter la demande.
  • Les en-têtes CORS ne sont pas retournés dans la réponse.

La commande suivante utilise curl pour émettre une demande OPTIONS avec des informations :

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Tester CORS avec le routage des points de terminaison et [HttpOptions]

L’activation de CORS sur une base par point de terminaison à l’aide de RequireCors ne prend actuellement pas en charge les demandes de contrôle préliminaire automatique. Considérez le code suivant qui utilise le routage de point de terminaison pour activer CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Les éléments suivants TodoItems1Controller fournissent des points de terminaison pour les tests :

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé.

Les boutons Supprimer [EnableCors] et GET [EnableCors] réussissent, car les points de terminaison ont [EnableCors] et répondent aux demandes préliminaires. Les autres points de terminaison échouent. Le bouton GET échoue, car javaScript envoie :

 headers: {
      "Content-Type": "x-custom-header"
 },

Les éléments suivants TodoItems2Controller fournissent des points de terminaison similaires, mais incluent du code explicite pour répondre aux demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé. Dans la liste déroulante Contrôleur, sélectionnez Contrôle préliminaire, puis Définir le contrôleur. Tous les appels CORS aux points de terminaison TodoItems2Controller réussissent.

Ressources supplémentaires

Par Rick Anderson et Kirk Larkin

Cet article explique comment activer CORS dans une application ASP.NET Core.

La sécurité du navigateur empêche une page Web d’envoyer des requêtes à un domaine différent de celui qui a servi la page Web. Cette restriction est appelée stratégie de même origine. La politique de la même origine empêche un site malveillant de lire les données sensibles d'un autre site. Parfois, vous souhaiterez peut-être autoriser d’autres sites à effectuer des demandes cross-origin à votre application. Pour plus d’informations, consultez l’article Mozilla CORS.

CORS (Cross Origin Resource Sharing) :

  • C’est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
  • N’est pas une fonctionnalité de sécurité, CORS assouplit la sécurité. Une API n’est pas plus sûre en autorisant CORS. Pour plus d'informations, consultez la section Fonctionnement de CORS.
  • Permet à un serveur d’autoriser explicitement certaines demandes multi-origines tout en refusant d’autres.
  • Est plus sûr et plus flexible que les techniques précédentes telles que JSONP.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Même origine

Deux URL ont la même origine si elles ont des schémas, des hôtes et des ports (RFC 6454) identiques.

Ces deux URL ont la même origine :

  • https://example.com/foo.html
  • https://example.com/bar.html

Ces URL ont des origines différentes des deux URL précédentes :

  • https://example.net : Domaine différent
  • https://www.example.com/foo.html : Sous-domaine différent
  • http://example.com/foo.html : Schéma différent
  • https://example.com:9000/foo.html : Port différent

Activez CORS

Il y a trois manières pour activer CORS :

L’utilisation de l’attribut [EnableCors] avec une stratégie nommée fournit le meilleur contrôle pour limiter les points de terminaison qui prennent en charge CORS.

Avertissement

UseCors doit être appelé dans le bon ordre. Pour plus d’informations, consultez Ordre des intergiciels (middleware). Par exemple, UseCors doit être appelé avant UseResponseCaching lors de l’utilisation de UseResponseCaching.

Chaque approche est détaillée dans les sections suivantes.

CORS avec une stratégie et un intergiciel nommés

CORS Middleware gère les demandes cross-origin. Le code suivant applique une stratégie CORS à tous les points de terminaison de l’application avec les origines spécifiées :

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors(MyAllowSpecificOrigins);

        // app.UseResponseCaching();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Le code précédent :

Avec le routage de point de terminaison, l’intergiciel CORS doit être configuré pour s’exécuter entre les appels à UseRouting et UseEndpoints.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

L’appel de méthode AddCors ajoute des services CORS au conteneur de service de l’application :

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

Pour plus d’informations, consultez Options de stratégie CORS dans ce document.

Les méthodes CorsPolicyBuilder peuvent être chaînées, comme indiqué dans le code suivant :

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
    });

    services.AddControllers();
}

Remarque : l’URL spécifiée ne doit pas contenir de barre oblique de fin (/). Si l’URL se termine par /, la comparaison retourne false et aucun en-tête n’est retourné.

CORS avec stratégie et intergiciel par défaut

Le code mis en surbrillance suivant active la stratégie CORS par défaut :

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Le code précédent applique la stratégie CORS par défaut à tous les points de terminaison du contrôleur.

Activer Cors avec le routage des points de terminaison

L’activation de CORS par point de terminaison à l’aide RequireCorsne prend pas en charge les demandes préliminaires automatiques. Pour plus d’informations, consultez ce problème GitHub et Test CORS avec routage de point de terminaison et [HttpOptions].

Avec le routage de point de terminaison, CORS peut être activé par point de terminaison à l’aide de l’ensemble RequireCors de méthodes d’extension :

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/echo",
                context => context.Response.WriteAsync("echo"))
                .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapControllers()
                     .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapGet("/echo2",
                context => context.Response.WriteAsync("echo2"));

            endpoints.MapRazorPages();
        });
    }
}

Dans le code précédent :

  • app.UseCors active l’intergiciel CORS. Étant donné qu’aucune stratégie par défaut n’a été configurée, app.UseCors() elle seule n’active pas CORS.
  • Les points de terminaison du contrôleur et /echo autorisent les demandes inter-origines à l’aide de la stratégie spécifiée.
  • Les points de terminaison pages /echo2et Razor n’autorisent pas les demandes cross-origin, car aucune stratégie par défaut n’a été spécifiée.

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage de point de terminaison avec RequireCors.

Pour obtenir des instructions sur le test du code similaire au précédent, consultez Test CORS avec routage de point de terminaison et [HttpOptions].

Activer CORS avec des attributs

L’activation de CORS avec l’attribut [EnableCors] et l’application d’une stratégie nommée uniquement aux points de terminaison qui nécessitent CORS fournissent le meilleur contrôle.

L’attribut [EnableCors] fournit une alternative à l’application globale de CORS. L’attribut [EnableCors] active CORS pour les points de terminaison sélectionnés, plutôt que tous les points de terminaison :

  • [EnableCors] spécifie la stratégie par défaut.
  • [EnableCors("{Policy String}")] spécifie une stratégie nommée.

L’attribut [EnableCors] peut s'appliquer aux éléments suivants :

  • Razor Page PageModel
  • Contrôleur
  • Méthode d’action du contrôleur

Différentes stratégies peuvent être appliquées aux contrôleurs, aux modèles de page ou aux méthodes d’action avec l’attribut [EnableCors]. Lorsque l’attribut [EnableCors] est appliqué à un contrôleur, à un modèle de page ou à une méthode d’action, et que CORS est activé dans l’intergiciel, les deux stratégies sont appliquées. Nous vous déconseillons de combiner des stratégies. Utilisez le [EnableCors]attribut ou middleware, pas les deux dans la même application.

Le code suivant applique une stratégie différente à chaque méthode :

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Le code suivant crée deux stratégies CORS :

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("Policy1",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                policy =>
                {
                    policy.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Pour le meilleur contrôle de la limitation des requêtes CORS :

  • Utilisez [EnableCors("MyPolicy")] avec une stratégie nommée.
  • Ne définissez pas de stratégie par défaut.
  • N’utilisez pas le routage de point de terminaison.

Le code de la section suivante répond à la liste précédente.

Consultez Test CORS pour obtenir des instructions sur le test du code similaire au code précédent.

Désactiver CORS

L’attribut [DisableCors] ne désactive pas CORS qui a été activé par le routage du point de terminaison.

Le code suivant définit la stratégie CORS "MyPolicy":

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

Le code suivant désactive CORS pour l’action GetValues2 :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Le code précédent :

Pour obtenir des instructions sur le test du code précédent, consultez Test CORS.

Options de stratégie CORS

Cette section décrit les différentes options qui peuvent être définies dans une stratégie CORS :

AddPolicy est appelé dans Startup.ConfigureServices. Pour certaines options, il peut être utile de lire d’abord la section Fonctionnement de CORS.

Définir les origines autorisées

AllowAnyOrigin: autorise les requêtes CORS de toutes les origines avec n’importe quel schéma (http ou https). AllowAnyOrigin n’est pas sécurisé, car n’importe quel site web peut effectuer des demandes cross-origin à l’application.

Notes

La spécification de AllowAnyOrigin et AllowCredentials est une configuration non sécurisée et peut entraîner une falsification de requête intersites. Le service CORS retourne une réponse CORS non valide lorsqu’une application est configurée avec les deux méthodes.

AllowAnyOrigin affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Origin . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

SetIsOriginAllowedToAllowWildcardSubdomains: définit la propriété IsOriginAllowed de la stratégie comme une fonction qui permet aux origines de correspondre à un domaine générique configuré lors de l’évaluation de l’autorisation de l’origine.

options.AddPolicy("MyAllowSubdomainPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

Définir les méthodes HTTP autorisées

AllowAnyMethod :

  • Autorise n’importe quelle méthode HTTP :
  • Affecte les demandes préliminaires et l’en-tête Access-Control-Allow-Methods. Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Définir les en-têtes de demande autorisés

Pour autoriser l’envoi d’en-têtes spécifiques dans une requête CORS, appelées en-têtes de demande d’auteur, appelez WithHeaders et spécifiez les en-têtes autorisés :

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

AllowAnyHeader affecte les demandes préliminaires et l’en-tête Access-Control-Request-Headers . Pour plus d’informations, consultez la section Demandes de contrôle préalable.

Une politique CORS Middleware correspondant à des en-têtes spécifiques spécifiés par WithHeaders n'est possible que si les en-têtes envoyés dans Access-Control-Request-Headers correspondent exactement aux en-têtes indiqués dans WithHeaders.

Pour instance, envisagez une application configurée comme suit :

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware refuse une demande préliminaire avec l’en-tête de demande suivant, car Content-Language (HeaderNames.ContentLanguage) n’est pas répertorié dans WithHeaders :

Access-Control-Request-Headers: Cache-Control, Content-Language

L’application retourne une réponse 200 OK, mais ne renvoie pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin.

Définir les en-têtes de réponse exposés

Par défaut, le navigateur n’expose pas tous les en-têtes de réponse à l’application. Pour plus d’informations, consultez Partage de ressources cross-origin du W3C (terminologie) : en-tête de réponse simple.

Les en-têtes de réponse disponibles par défaut sont les suivants :

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La spécification CORS appelle ces en-têtes de réponse simples. Pour rendre d’autres en-têtes disponibles pour l’application, appelez WithExposedHeaders :

options.AddPolicy("MyExposeResponseHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .WithExposedHeaders("x-custom-header");
    });

Informations d’identification dans les demandes cross-origin

Les informations d’identification nécessitent une gestion spéciale dans une demande CORS. Par défaut le navigateur n’envoie pas d’informations d’identification avec une requête de cross-origin. Les informations d’identification incluent des cookieschémas d’authentification s et HTTP. Pour envoyer des informations d’identification avec une requête cross-origin, le client doit définir XMLHttpRequest.withCredentials sur true.

En utilisant XMLHttpRequest directement :

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Utilisation de jQuery :

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Utilisation de l’API Fetch :

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Le serveur doit autoriser les informations d’identification. Pour autoriser les informations d’identification cross-origin, appelez AllowCredentials :

options.AddPolicy("MyMyAllowCredentialsPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .AllowCredentials();
    });

La réponse HTTP inclut un Access-Control-Allow-Credentials en-tête, qui indique au navigateur que le serveur autorise les informations d’identification pour une requête cross-origin.

Si le navigateur envoie des informations d’identification mais que la réponse n’inclut pas d’en-tête valide Access-Control-Allow-Credentials, le navigateur n’expose pas la réponse à l’application et la demande cross-origin échoue.

L’autorisation des informations d’identification cross-origin est un risque pour la sécurité. Un site web d’un autre domaine peut envoyer les informations d’identification d’un utilisateur connecté à l’application pour le compte de l’utilisateur à l’insu de l’utilisateur.

La spécification CORS indique également que la définition des origines sur "*" (toutes les origines) n’est pas valide si l’en-tête Access-Control-Allow-Credentials est présent.

Demandes préliminaires

Pour certaines requêtes CORS, le navigateur envoie une requête OPTIONS supplémentaire avant d’effectuer la requête réelle. Cette requête est appelée demande préliminaire. Le navigateur peut ignorer la demande préliminaire si toutesles conditions suivantes sont remplies :

  • La méthode de demande est GET, HEAD ou POST.
  • L’application ne définit pas d’en-têtes de demande autres que Accept, Accept-Language, Content-Language, Content-Typeou Last-Event-ID.
  • L'en-tête Content-Type, s'il est défini, a l'une des valeurs suivantes :
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La règle sur les en-têtes de demande définis pour la demande cliente s’applique aux en-têtes définis par l’application en appelant setRequestHeader sur l’objet XMLHttpRequest. La spécification CORS appelle ces en-têtes des en-têtes de demande d’auteur. La règle ne s’applique pas aux en-têtes que le navigateur peut définir, tels que User-Agent, Hostou Content-Length.

Voici un exemple de réponse similaire à la demande préliminaire effectuée à partir du bouton [Put test] dans la section Test CORS de ce document.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La requête préliminaire utilise la méthode HTTP OPTIONS. Il peut inclure les en-têtes suivants :

Si la demande préliminaire est refusée, l’application retourne une réponse 200 OK, mais ne définit pas les en-têtes CORS. Par conséquent, le navigateur ne tente pas la requête cross-origin. Pour obtenir un exemple de demande préliminaire refusée, consultez la section Test CORS de ce document.

À l’aide des outils F12, l’application console affiche une erreur similaire à l’une des suivantes, en fonction du navigateur :

  • Firefox : Requête cross-origin bloquée : la même stratégie d’origine interdit la lecture de la ressource distante à l’adresse https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motif : la demande CORS n’a pas réussi). En savoir plus
  • Basé sur Chromium : l’accès à l’extraction https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 à partir de l’origine https://cors3.azurewebsites.net a été bloqué par la stratégie CORS : la réponse à la demande préliminaire ne passe pas la vérification de contrôle d’accès : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Pour autoriser des en-têtes spécifiques, appelez WithHeaders :

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

Pour autoriser tous les en-têtes de demande d’auteur, appelez AllowAnyHeader :

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

Les navigateurs ne sont pas cohérents dans la façon dont ils définissent Access-Control-Request-Headers. Si :

  • Les en-têtes sont définis sur autre chose que "*"
  • AllowAnyHeader est appelé : incluez au moins Accept, Content-Typeet Origin, ainsi que tous les en-têtes personnalisés que vous souhaitez prendre en charge.

Code de demande de contrôle préliminaire automatique

Lorsque la stratégie CORS est appliquée :

  • Globalement, en appelant app.UseCors dans Startup.Configure.
  • Utilisation de l’attribut [EnableCors].

ASP.NET Core répond à la demande d’OPTIONS en pré-vol.

L’activation de CORS sur une base par point de terminaison à l’aide de RequireCors ne prend actuellement pas en charge les demandes de contrôle préliminaire automatique.

La section Test CORS de ce document illustre ce comportement.

Attribut [HttpOptions] pour les demandes de contrôle préalable

Lorsque CORS est activé avec la stratégie appropriée, ASP.NET Core répond généralement automatiquement aux demandes de contrôle préalable CORS. Dans certains scénarios, cela peut ne pas être le cas. Par exemple, l’utilisation de CORS avec le routage de point de terminaison.

Le code suivant utilise l’attribut [HttpOptions] pour créer des points de terminaison pour les demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Pour obtenir des instructions sur le test du code précédent, consultez Test CORS avec routage de point de terminaison et [HttpOptions ].

Définir l’heure d’expiration de la préversion

L’en-tête Access-Control-Max-Age spécifie la durée pendant laquelle la réponse à la demande de contrôle préliminaire peut être mise en cache. Pour définir cet en-tête, appelez SetPreflightMaxAge :

options.AddPolicy("MySetPreflightExpirationPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

Fonctionnement de CORS

Cette section décrit ce qui se passe dans une requête CORS au niveau des messages HTTP.

  • CORS n’est pas une fonctionnalité de sécurité. CORS est une norme W3C qui permet à un serveur d’assouplir la stratégie de même origine.
    • Par exemple, un acteur malveillant peut utiliser des scripts intersite (XSS) sur votre site et exécuter une demande intersite à son site compatible CORS pour voler des informations.
  • Une API n’est pas plus sûre en autorisant CORS.
    • Il appartient au client (navigateur) d’appliquer CORS. Le serveur exécute la requête et retourne la réponse. C’est le client qui retourne une erreur et bloque la réponse. Par exemple, l’un des outils suivants affiche la réponse du serveur :
  • Il s’agit d’un moyen pour un serveur d’autoriser les navigateurs à exécuter une requête XHRouFetch API d’origine croisée qui, sinon, serait interdite.
    • Les navigateurs sans CORS ne peuvent pas effectuer de requêtes inter-origines. Avant CORS, JSONP était utilisé pour contourner cette restriction. JSONP n’utilise pas XHR, il utilise la balise <script> pour recevoir la réponse. Les scripts sont autorisés à être chargés entre origines.

La spécification CORS a introduit plusieurs nouveaux en-têtes HTTP qui activent les requêtes d’origine croisée. Si un navigateur prend en charge CORS, il définit automatiquement ces en-têtes pour les demandes d’origine croisée. Le code JavaScript personnalisé n’est pas nécessaire pour activer CORS.

Le Bouton de test PUT sur l’exemple déployé

Voici un exemple de demande d’origine croisée du bouton de test Valeurs vers https://cors1.azurewebsites.net/api/values. En-tête Origin :

  • Fournit le domaine du site qui effectue la demande.
  • Est obligatoire et doit être différent de l’hôte.

En-têtes généraux

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

En-têtes de réponse

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

Dans les requêtesOPTIONS, le serveur définit l’en-tête d’en-têtes de réponseAccess-Control-Allow-Origin: {allowed origin} dans la réponse. Par exemple, l’exemple déployé, le Supprimer [EnableCors]boutonOPTIONS contient les en-têtes suivants :

En-têtes généraux

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

En-têtes de réponse

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

En-têtes de requête

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Dans les en-têtes de réponse précédents, le serveur définit l’en-tête Access-Control-Allow-Origin dans la réponse. La valeur https://cors1.azurewebsites.net de cet en-tête correspond à l’en-tête Origin de la demande.

Si AllowAnyOrigin est appelé,Access-Control-Allow-Origin: *, la valeur de caractère générique est retournée. AllowAnyOrigin autorise n’importe quelle origine.

Si la réponse n’inclut pas l’en-tête Access-Control-Allow-Origin, la demande d’origine croisée échoue. Plus précisément, le navigateur interdit la demande. Même si le serveur retourne une réponse réussie, le navigateur ne rend pas la réponse disponible pour l’application cliente.

Afficher les demandes OPTIONS

Par défaut, les navigateurs Chrome et Edge n’affichent pas les requêtes OPTIONS sous l’onglet réseau des outils F12. Pour afficher les demandes OPTIONS dans ces navigateurs :

  • chrome://flags/#out-of-blink-cors ou edge://flags/#out-of-blink-cors
  • désactivez l’indicateur.
  • redémarrer.

Firefox affiche les demandes OPTIONS par défaut.

CORS dans IIS

Lors du déploiement sur IIS, CORS doit s’exécuter avant l’authentification Windows si le serveur n’est pas configuré pour autoriser l’accès anonyme. Pour prendre en charge ce scénario, le module IIS CORS doit être installé et configuré pour l’application.

Tester CORS

L’exemple de téléchargement contient du code pour tester CORS. Consultez Guide pratique pour télécharger. L’exemple est un projet d’API avec Razor Pages ajoutées :

public class StartupTest2
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

Avertissement

WithOrigins("https://localhost:<port>"); doit uniquement être utilisé pour tester un exemple d’application similaire à l’exemple de code de téléchargement.

ValuesController Voici les points de terminaison pour les tests :

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

Testez l’exemple de code précédent à l’aide de l’une des approches suivantes :

  • Utilisez l’exemple d’application déployé sur https://cors3.azurewebsites.net/. Il n’est pas nécessaire de télécharger l’exemple.
  • Exécutez l’exemple avec dotnet run à l’aide de l’URL par défaut de https://localhost:5001.
  • Exécutez l’exemple à partir de Visual Studio avec le port défini sur 44398 pour une URL de https://localhost:44398.

Utilisation d’un navigateur avec les outils F12 :

  • Sélectionnez le bouton Valeurs et passez en revue les en-têtes sous l’onglet Réseau.

  • Sélectionnez le bouton test PUT. Consultez Afficher les demandes OPTIONS pour obtenir des instructions sur l’affichage de la demande OPTIONS. Le test PUT crée deux requêtes, une demande de contrôle préliminaire OPTIONS et la demande PUT.

  • Sélectionnez le bouton GetValues2 [DisableCors] pour déclencher une demande CORS ayant échoué. Comme indiqué dans le document, la réponse retourne 200 réussites, mais la demande CORS n’est pas effectuée. Sélectionnez l’onglet Console pour voir l’erreur CORS. Selon le navigateur, une erreur similaire à celle indiquée ci-après s'affiche :

    L’accès à l’extraction à 'https://cors1.azurewebsites.net/api/values/GetValues2' partir de l’origine 'https://cors3.azurewebsites.net' a été bloqué par la stratégie CORS : aucun en-tête « Access-Control-Allow-Origin » n’est présent sur la ressource demandée. Si une réponse opaque répond à vos besoins, définissez le mode de la requête sur « no-cors » pour extraire la ressource avec CORS désactivé.

Les points de terminaison compatibles CORS peuvent être testés avec un outil, tel que curl, Fiddler ou Postman. Lors de l’utilisation d’un outil, l’origine de la demande spécifiée par l’en-tête Origin doit différer de celle de l’hôte qui reçoit la demande. Si la requête n’est pas d’origine croisée basée sur la valeur de l’en-tête Origin :

  • Il n’est pas nécessaire d’utiliser l’intergiciel CORS pour traiter la demande.
  • Les en-têtes CORS ne sont pas retournés dans la réponse.

La commande suivante utilise curl pour émettre une demande OPTIONS avec des informations :

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Tester CORS avec le routage des points de terminaison et [HttpOptions]

L’activation de CORS sur une base par point de terminaison à l’aide de RequireCors ne prend actuellement pas en charge les demandes de contrôle préliminaire automatique. Considérez le code suivant qui utilise le routage de point de terminaison pour activer CORS:

public class StartupEndPointBugTest
{
    readonly string MyPolicy = "_myPolicy";

    // .WithHeaders(HeaderNames.ContentType, "x-custom-header")
    // forces browsers to require a preflight request with GET

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyPolicy,
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com",
                                        "https://cors1.azurewebsites.net",
                                        "https://cors3.azurewebsites.net",
                                        "https://localhost:44398",
                                        "https://localhost:5001")
                           .WithHeaders(HeaderNames.ContentType, "x-custom-header")
                           .WithMethods("PUT", "DELETE", "GET", "OPTIONS");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers().RequireCors(MyPolicy);
            endpoints.MapRazorPages();
        });
    }
}

Les éléments suivants TodoItems1Controller fournissent des points de terminaison pour les tests :

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé.

Les boutons Supprimer [EnableCors] et GET [EnableCors] réussissent, car les points de terminaison ont [EnableCors] et répondent aux demandes préliminaires. Les autres points de terminaison échouent. Le bouton GET échoue, car javaScript envoie :

 headers: {
      "Content-Type": "x-custom-header"
 },

Les éléments suivants TodoItems2Controller fournissent des points de terminaison similaires, mais incluent du code explicite pour répondre aux demandes OPTIONS :

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Testez le code précédent à partir de la page de test de l’exemple déployé. Dans la liste déroulante Contrôleur, sélectionnez Contrôle préliminaire, puis Définir le contrôleur. Tous les appels CORS aux points de terminaison TodoItems2Controller réussissent.

Ressources supplémentaires