Wstęp
CORS (Cross-Origin Resource Sharing) to mechanizm bezpieczeństwa stosowany w przeglądarkach, który pozwala kontrolować, które domeny mogą uzyskać dostęp do zasobów serwera. Jest on szczególnie istotny w przypadku aplikacji webowych, w których front-end i back-end znajdują się na różnych domenach – a tak zdarza się coraz częsciej.
Jak działa CORS?
W przypadku niektórych requestów, jeśli zajrzymy do zakładki „Network” w przeglądarce, możemy zauważyć „podwójne” request. Jedno z nich to preflight, czyli wstępne zapytanie sprawdzające, czy serwer zezwala na daną operację. Żądanie to jest wysyłane z metodą HTTP OPTIONS
.

W przypadku gdy CORS nie jest poprawnie skonfigurowany dostajemy błąd

Konfiguracja przy użyciu WebApi
Aby CORS działał poprawnie, musimy skonfigurować Policy
po stronie serwera, które określa, na co zezwalamy. Na IServiceCollection
wywołujemy metodę AddCors
, w której definiujemy Policy
. Następnie, w kolejnym kroku, wywołujemy metodę UseCors
na IApplicationBuilder
, gdzie podajemy nazwę Policy
, z której chcemy skorzystać.
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
app.UseCors("AllowAllOrigins");
Po dodaniu powyższych kilku linijek kodu, zamiast błędu CORS możemy zauważyć, że API zwraca już dane. Najprościej jest sobie to przetestować jakimś fetchem, utworzyłem prosty kawałek kodu do testów:
<script type="text/javascript">
fetch('https://localhost:7227/WeatherForecast');
</script>
Komentarz
Powyższy kod nie powinien trafić na produkcję 😀 w przypadku, gdy lokalnie pojawiają się problemy z CORS, a konfiguracja tego zajmuje bardzo dużo czasu, uważam, że to strata czasu i właśnie takie Policy
powinniśmy ustawić. Zawsze powtarzam jedną rzecz, jeśli coś blokuje waszą pracę, a w szczególności, w przypadku gdy się uczycie nowych rzeczy, rozwiązujcie takie blokery jak najszybciej, będzie później czas na poprawę tego. I wiem, że to słowa mogą być różnie zinterpretowane, nie mam zamiaru zachęcać, żeby zawsze iść na skróty, ale jeśli rzeczy typu CORS (który pojawia się bardzo często!!) mają blokować waszą naukę, to idźcie na skróty. Śmieszna sprawa, bo mam wrażenie, że każdy zna CORS, spotkał się z tym wiele razy ale problemy nadal występują 😀 Ostatnio powstaje nowy projekt, u nas w firmie i też ostatnio był update, że „coś tam jeszcze z CORSem działają :D”.
Dobre praktyki
Warto jednak mieć większą kontrolę nad różnymi Policy
, które definiujemy. Powyższą politykę można skonfigurować tylko dla środowiska Development. W przypadku pozostałych środowisk chcielibyśmy już mieć większą kontrolę. Możemy zarządzać między innymi:
- Origins – adresy, z których dozwolony jest dostęp do zasobów.
- Headers – nagłówki HTTP akceptowane od klienta.
- Methods – dozwolone metody HTTP.
Dzięki temu możemy na przykład stworzyć Policy
dla konkretnych hostów, w której dodatkowo wymagamy od nich określonych nagłówków.
Poniżej znajduje się przykład, jak można rozszerzyć rejestrację Policy
. To do czego zachęcam, to żeby nie posługiwać się stringami a constami, tak żeby zminimalizować ryzyko pomyłki. Dodatkowo, bardzo pomocna klasa do nagłówków, która zawiera predefiniowane, najczęsciej używane – HeaderNames
.
public const string AllowAllOrigins = nameof(AllowAllOrigins);
public const string AllowSpecificMethods = nameof(AllowSpecificMethods);
public const string HostAPolicy = nameof(HostAPolicy);
builder.Services.AddCors(options =>
{
options.AddPolicy(AllowAllOrigins, policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
options.AddPolicy(AllowSpecificMethods, policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.WithMethods("GET", "POST");
});
options.AddPolicy(HostAPolicy, policy =>
{
policy.WithOrigins("http://a-host.com")
.WithHeaders(HeaderNames.Authorization)
.WithMethods("DELETE", "PUT");
});
});
Rozszerzyłem też przykład z fetch do testowania
<script type="text/javascript">
fetch('https://localhost:7227/WeatherForecast', {method: 'POST'});
fetch('https://localhost:7227/WeatherForecast', {method: 'GET'});
fetch('https://localhost:7227/WeatherForecast', {method: 'PUT'});
</script>
Rezultat jaki dostaję, GET i POST przeszły, w przypadku PUT dostajemy CORS error – zgodnie z oczekiwaniami.

Przykład jak można zarejestrować CORS dla lokalnego developmentu oraz dla produkcji (w przypadku większej ilości środowisk, setup może być bardziej skomplikowany)
if (app.Environment.IsDevelopment())
app.UseCors(AllowAllOrigins);
else
{
app.UseCors(AllowSpecificMethods);
app.UseCors(HostAPolicy);
}