﻿# Налаштування заголовків перенаправлення в .NET при роботі з Azure Application Gateway

- Canonical URL: https://taraskovalenko.github.io/posts/agw-hostname/
- Published: 2025-04-25
- Categories: .net, C#, azure, Application Gateway
- Tags: .net, C#, OpenID, Azure, Application Gateway, Middleware

Коли ми розгортаємо .NET додаток на `Azure App Service` та налаштовуємо доступ до нього через `Azure Application Gateway`, виникає типова проблема з визначенням правильного хосту та схеми запиту.
Суть проблеми в тому, що запит проходить кілька рівнів до вашого додатка:

- Користувач відправляє запит на `https://myapp.example.com`
- Запит потрапляє до `Application Gateway`
- `Application Gateway` перенаправляє запит до `App Service`
- `App Service` передає запит вашому додатку

На кожному етапі інформація про оригінальний запит (IP-адреса користувача, схема, хост) може бути втрачена або замінена на внутрішню. В результаті, коли запит досягає вашого додатка, `HttpContext` містить не ті дані, які були в оригінальному запиті.
Наприклад, якщо ваш користувач заходить через `https://myapp.example.com`, то до вашого додатка запит може дійти як `http://internal-appservice-ip:port`. Це призводить до таких проблем:

- Неправильна генерація URL - всі посилання, які генерує ваш додаток, будуть містити неправильний хост та схему
- Помилки автентифікації - особливо з `OpenIddict` або іншими реалізаціями `OpenID Connect`, які суворо перевіряють URL перенаправлення
- Проблеми з `CORS` - неправильне визначення Origin-заголовка
- Помилки в роботі `middleware` - багато компонентів .NET залежать від правильного визначення схеми та хоста

## Рішення

.NET має вбудований механізм для обробки перенаправлених заголовків, який можна налаштувати для коректної роботи з проксі-серверами.

Нижче наведено код, який вирішує проблему:

```csharp
services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = 
       ForwardedHeaders.XForwardedFor | 
       ForwardedHeaders.XForwardedProto |
       ForwardedHeaders.XForwardedHost;
    
    options.ForwardedHostHeaderName = "X-Original-Host";
    
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});
```

Розберемо, що відбувається в цьому коді:

### Налаштування ForwardedHeaders

```csharp
options.ForwardedHeaders = 
   ForwardedHeaders.XForwardedFor | 
   ForwardedHeaders.XForwardedProto |
   ForwardedHeaders.XForwardedHost;
```

Тут ми вказуємо, які саме заголовки перенаправлення повинні оброблятися:

- `XForwardedFor` - визначає оригінальну IP-адресу клієнта
- `XForwardedProto` - визначає оригінальний протокол (http або https)
- `XForwardedHost` - визначає оригінальний хост, вказаний у запиті

### Налаштування додаткового заголовка хосту

```csharp
options.ForwardedHostHeaderName = "X-Original-Host";
```

Цей рядок є критично важливим для нашого сценарію з `Azure Application Gateway`. Він вказує .NET дивитися на нестандартний заголовок `X-Original-Host` для визначення оригінального хосту.
Часто `Azure Application Gateway` або інші проксі налаштовані на передачу оригінального хосту саме в цьому заголовку замість стандартного `X-Forwarded-Host`.

### Очищення списків довірених мереж і проксі

```csharp
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
```

За замовчуванням, .NET приймає перенаправлені заголовки тільки від довірених проксі для безпеки. Очищаючи ці списки, ми дозволяємо приймати заголовки від будь-яких джерел.

## Приклад проблеми з OpenIddict

Розглянемо конкретний приклад проблеми з `OpenIddict`:

Припустимо, ваш додаток розгорнуто за `Application Gateway`, і користувачі отримують доступ до нього за адресою `https://myapp.example.com`.
Однак, сам додаток бачить запити, що надходять від `Application Gateway` з локальної мережі, наприклад, як `http://internal-ip:port`.
При налаштуванні `OpenIddict`, ви вказуєте URL перенаправлення як `https://myapp.example.com/signin-callback`. Коли користувач проходить автентифікацію, `OpenIddict` намагається перевірити, чи збігається URL перенаправлення з налаштованим. Але оскільки додаток "думає", що працює за адресою `http://internal-ip:port`, він генерує URL перенаправлення як `http://internal-ip:port/signin-callback`.

Результат: несумісність URL призводить до помилки автентифікації з повідомленням на кшталт `"Invalid redirect_uri"`.

## Застосування рішення в Startup.cs

Для повного вирішення проблеми, крім налаштування опцій, необхідно також додати middleware для обробки перенаправлених заголовків у метод Configure:

```csharp
 // Важливо додати це на самому початку пайпайна обробки запитів
app.UseForwardedHeaders();
```

## Особливості налаштування для Azure App Service

Додатково варто зазначити, що коли ваш додаток розгорнуто на `Azure App Service` за `Application Gateway`, проблема з перенаправленням заголовків стає особливо актуальною. `App Service` має власну інфраструктуру балансування навантаження, яка додає ще один рівень проксіювання:

`Користувач → Application Gateway → App Service infrastructure → Ваш додаток`

Кожен рівень може модифікувати заголовки, тому правильне налаштування `ForwardedHeadersOptions` є критичним для забезпечення коректної роботи додатка в такій архітектурі.
Також при розгортанні на `App Service` рекомендується перевірити налаштування `ssl termination`. Якщо SSL-термінація відбувається на рівні `Application Gateway`, то до вашого додатка запити можуть надходити по `HTTP`, навіть якщо клієнт використовує HTTPS.
У такому випадку правильна обробка `XForwardedProto` забезпечить коректне визначення схеми.

## Висновок

Правильне налаштування перенаправлених заголовків є критично важливим при розгортанні .NET додатків на `Azure App Service` за проксі-серверами, особливо якщо ви використовуєте протоколи автентифікації, такі як `OpenID Connect`.
Наведене рішення дозволяє додатку коректно визначати оригінальну адресу запиту, що забезпечує правильну роботу URL-генерації, автентифікації та інших компонентів, які залежать від точного визначення хоста та схеми запиту.

{: .prompt-info }
Не забудьте в реальному сценарії налаштувати список довірених проксі для підвищення безпеки вашого додатку.

