# Включение CORS (Cross-Origin Resource Sharing) в Sisk

Source: https://docs.sisk-framework.org/ru/docs/features/cors.html

Sisk предоставляет инструмент, который может быть полезен для обработки [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) при публичном предоставлении вашего сервиса. Эта возможность не является частью протокола HTTP, а представляет собой специфическую функцию веб‑браузеров, определённую W3C. Этот механизм безопасности препятствует веб‑странице выполнять запросы к другому домену, отличному от того, с которого была получена сама страница. Поставщик сервиса может разрешить доступ к своим ресурсам определённым доменам или только одному.

## Один и тот же источник

Чтобы ресурс был идентифицирован как «один и тот же источник», запрос должен содержать заголовок [Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin):

```http
GET /api/users HTTP/1.1
Host: example.com
Origin: http://example.com
...
```

А удалённый сервер должен ответить заголовком [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) со значением, совпадающим с запрошенным origin:

```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
...
```

Эта проверка **явная**: хост, порт и протокол должны совпадать с запрошенными. Рассмотрим пример:

- Сервер отвечает, что его `Access-Control-Allow-Origin` равен `https://example.com`:
    - `https://example.net` – домен отличается.
    - `http://example.com` – схема отличается.
    - `http://example.com:5555` – порт отличается.
    - `https://www.example.com` – хост отличается.

В спецификации допускается только синтаксис для обоих заголовков, как для запросов, так и для ответов. Путь URL игнорируется. Порт также опускается, если он является портом по умолчанию (80 для HTTP и 443 для HTTPS).

```http
Origin: null
Origin: <scheme>://<hostname>
Origin: <scheme>://<hostname>:<port>
```

## Включение CORS

Нативно у вас есть объект [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) внутри вашего [ListeningHost](https://docs.sisk-framework.org/api/Sisk.Core.Http.ListeningHost.md).

Вы можете настроить CORS при инициализации сервера:

```csharp
static async Task Main(string[] args)
{
    using var app = HttpServer.CreateBuilder()
        .UseCors(new CrossOriginResourceSharingHeaders(
            allowOrigin: "http://example.com",
            allowHeaders: ["Authorization"],
            exposeHeaders: ["Content-Type"]))
        .Build();

    await app.StartAsync();
}
```

Код выше отправит следующие заголовки для **всех ответов**:

```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Headers: Authorization
Access-Control-Expose-Headers: Content-Type
```

Эти заголовки должны отправляться во всех ответах клиенту, включая ошибки и перенаправления.

Вы можете заметить, что класс [CrossOriginResourceSharingHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.md) имеет два похожих свойства: [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md) и [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md). Обратите внимание, что одно — во множественном числе, другое — в единственном.

- Свойство **AllowOrigin** статично: только указанный вами origin будет отправлен во всех ответах.
- Свойство **AllowOrigins** динамично: сервер проверяет, содержится ли origin запроса в этом списке. Если найдено, он будет отправлен в ответе для данного origin.

Когда разрешённый origin зависит от запроса, то есть когда вы используете **AllowOrigins** или значение [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md), описанное ниже, Sisk также добавляет заголовок `Vary: Origin`, чтобы кэши не переиспользовали ответ для другого origin.

### Подстановочные знаки и автоматические заголовки

В качестве альтернативы можно использовать подстановочный символ (`*`) в origin ответа, указывая, что любой origin разрешён для доступа к ресурсу. Однако такое значение недопустимо для запросов, содержащих учётные данные (заголовки авторизации), и такая операция [приведёт к ошибке](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS/Errors/CORSNotSupportingCredentials).

Обойти эту проблему можно, явно перечислив разрешённые origin через свойство [AllowOrigins](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigins.md) или используя константу [AutoAllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoAllowOrigin.md) в значении [AllowOrigin](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AllowOrigin.md). Это «магическое» свойство задаст заголовок `Access-Control-Allow-Origin` со значением, совпадающим с заголовком `Origin` запроса.

Также можно использовать [AutoFromRequestMethod](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestMethod.md) и [AutoFromRequestHeaders](https://docs.sisk-framework.org/api/Sisk.Core.Entity.CrossOriginResourceSharingHeaders.AutoFromRequestHeaders.md) для поведения, аналогичного `AllowOrigin`, которое автоматически отвечает на основе полученных заголовков.

```csharp
using var host = HttpServer.CreateBuilder()
    .UseCors(new CrossOriginResourceSharingHeaders(
        
        // Отвечает в зависимости от заголовка Origin запроса
        allowOrigin: CrossOriginResourceSharingHeaders.AutoAllowOrigin,
        
        // Отвечает в зависимости от заголовка Access-Control-Request-Method или метода запроса
        allowMethods: [CrossOriginResourceSharingHeaders.AutoFromRequestMethod],

        // Отвечает в зависимости от заголовка Access-Control-Request-Headers или отправленных заголовков
        allowHeaders: [CrossOriginResourceSharingHeaders.AutoFromRequestHeaders],

        exposeHeaders: [HttpKnownHeaderNames.ContentType, "X-Authenticated-Account-Id"],
        allowCredentials: true))
    .Build();
```

## Другие способы применения CORS

Если вы работаете с [service providers](https://docs.sisk-framework.org/ru/docs/extensions/service-providers.md), вы можете переопределить значения, определённые в файле конфигурации:

```csharp
static async Task Main(string[] args)
{
    using var app = HttpServer.CreateBuilder()
        .UsePortableConfiguration(...)
        .UseCors(cors => {
            // Перезапишет origin, определённый в конфигурации
            // файл.
            cors.AllowOrigin = "http://example.com";
        })
        .Build();

    await app.StartAsync();
}
```

## Отключение CORS на конкретных маршрутах

Свойство `UseCors` доступно как для маршрутов, так и для всех атрибутов маршрутов и может быть отключено следующим примером:

```csharp
[RoutePrefix("api/widgets")]
public class WidgetController : Controller {

    // GET /api/widgets/colors
    [RouteGet("/colors", UseCors = false)]
    public IEnumerable<string> GetWidgets() {
        return new[] { "Green widget", "Red widget" };
    }
}
```

## Замена значений в ответе

Вы можете явно заменить или удалить значения в действии роутера:

```csharp
[RoutePrefix("api/widgets")]
public class WidgetController : Controller {

    public IEnumerable<string> GetWidgets(HttpRequest request) {

        // Удаляет заголовок Access-Control-Allow-Credentials
        request.Context.OverrideHeaders.AccessControlAllowCredentials = string.Empty;
        
        // Заменяет заголовок Access-Control-Allow-Origin
        request.Context.OverrideHeaders.AccessControlAllowOrigin = "https://contorso.com";

        return new[] { "Green widget", "Red widget" };
    }
}
```

## Preflight‑запросы

Preflight‑запрос — это запрос метода [OPTIONS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/OPTIONS), который клиент отправляет перед фактическим запросом.

Когда запрос с методом `OPTIONS` совпадает с путём маршрута, но для `OPTIONS` явно не настроен маршрут, сервер Sisk отвечает `200 OK` и соответствующими CORS‑заголовками, после чего клиент может выполнить фактический запрос.

- Если маршрут явно сопоставлен с [RouteMethod.Options](https://docs.sisk-framework.org/api/Sisk.Core.Routing.RouteMethod.md), например с помощью `MapOptions`, этот маршрут обрабатывает запрос вместо автоматического ответа.
- Если у маршрута, совпадающего с путём, установлено `UseCors = false`, автоматический ответ всё равно отправляется, но без CORS‑заголовков.

> [!NOTE]
> До версии Sisk 1.7 автоматический ответ на preflight‑запрос всегда включал CORS‑заголовки, даже для маршрутов с `UseCors = false`.

## Отключение CORS глобально

Это невозможно. Чтобы не использовать CORS, просто не настраивайте его.
