> ## Documentation Index
> Fetch the complete documentation index at: https://veriqa.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Совместимость с клиентами

> Квирки совместимости: именованные послабления формы запроса для конкретного клиента.

Некоторые OIDC-клиенты отправляют запросы в форме, которая расходится с RFC. Veriqa строгая и
такой запрос отвергает — это правильное поведение по умолчанию, но иногда исправить клиента вы не
можете: чужой плагин, коробочная CMS, вендорская сборка.

**Квирк совместимости** — именованное послабление формы запроса, включаемое **для одного
клиента**. Каждый квирк точечный, выключен по умолчанию и живёт с заранее объявленным условием
снятия: когда апстрим-дефект починят, ключ убирают вместе с его обработчиком.

<Warning>
  Квирк — это ветка поведения, а не настройка. Включайте его, только если конкретный клиент
  действительно не может отправить корректный запрос, и снимайте, как только условие снятия
  выполнено. Каждый включённый квирк Veriqa сообщает предупреждением в логе при старте.
</Warning>

## Почему по клиенту, а не глобально

Послабление, включённое глобально, ослабляет проверку для **всех** приложений — включая те, что
шлют корректные запросы, и те, что появятся в конфигурации завтра. Строгость проверки перестаёт
быть свойством развёртывания и становится свойством самого слабого его участника.

Поэтому квирк адресуется по `ClientId`: клиент со сломанным плагином получает послабление, все
остальные продолжают проверяться строго. Это же делает послабление видимым — в конфигурации
написано, **какому** приложению и **что** именно разрешено сверх RFC.

## Как включить

Включение двухступенчатое: мало перечислить квирк у клиента — нужно ещё открыть гейт
развёртывания. Пока гейт закрыт, набор квирков клиента пересекается с пустым набором уровня ядра,
и послабление не применяется.

```json appsettings.json theme={null}
{
  "Veriqa": {
    "OpenIddict": {
      "CompatibilityQuirksAllowApplicationOverride": true,
      "Clients": [
        {
          "ClientId": "my-wordpress-site",
          "DisplayName": "My WordPress Site",
          "AllowedRedirectUris": [ "https://example.com/oidc/callback" ],
          "AllowedScopes": [ "openid", "profile", "email" ],
          "CompatibilityQuirks": [ "drop-scope-on-code-exchange" ]
        }
      ]
    }
  }
}
```

| Ключ | Уровень | Дефолт | Значение |
| - | - | - | - |
| `Veriqa:OpenIddict:CompatibilityQuirksAllowApplicationOverride` | развёртывание | `false` | Гейт: пока закрыт, ни один квирк не действует, что бы ни стояло у клиентов |
| `Veriqa:OpenIddict:Clients[].CompatibilityQuirks` | клиент | не задан | Список ключей квирков из закрытого набора |
| `Veriqa:OpenIddict:Clients[].CompatibilityProfile` | клиент | не задан | Имя профиля из закрытого набора: разворачивается в ключи квирков и объединяется с `CompatibilityQuirks` |

Две ступени существуют затем, чтобы конфигурация отдельного клиента не могла в одиночку ослабить
проверку: за общий потолок отвечает тот, кто владеет развёртыванием, а за конкретное приложение —
запись клиента.

### Профиль вместо перечня ключей

Вместо того чтобы выписывать ключи по одному, можно назвать профиль — движок и плагин, которые у вас
стоят. Профиль разворачивается в те же ключи, проходит ту же валидацию, тот же гейт и тот же потолок:
это сокращение записи, а не отдельный механизм.

```json appsettings.json theme={null}
{
  "ClientId": "my-wordpress-site",
  "CompatibilityProfile": "wordpress-openid-connect-generic"
}
```

| Профиль | Что включает |
| - | - |
| `wordpress-openid-connect-generic` | `drop-scope-on-code-exchange` |
| `moodle-auth-oidc` | `drop-resource-parameter` |

Профиль и явный перечень можно сочетать — ключи объединяются. Неизвестное имя профиля роняет старт
с перечнем допустимых, ровно как неизвестный ключ квирка: опечатка не может молча оставить
послабление выключенным.

В профиль входят **только послабления формы запроса**. Ничто, меняющее claims, профилем включаться не
может: включая совместимость со сломанным клиентом, интегратор не должен получить в придачу другое
утверждение о том, кто вошёл.

## Как выключить

Уберите ключ из `CompatibilityQuirks` этого клиента — послабление перестаёт действовать при
следующем старте. Чтобы разом снять все квирки развёртывания, не трогая записи клиентов, закройте
гейт: `CompatibilityQuirksAllowApplicationOverride: false`.

Специально выключать ничего не нужно: **дефолт — выключено у всех**. Свежее развёртывание не
применяет ни одного квирка, пока вы не откроете гейт и не перечислите ключ явно.

## Что Veriqa сообщает при старте

* **Квирк действует** — предупреждение со списком «клиент → включённые квирки». Это ожидаемый шум:
  включённое послабление не должно быть тихим.
* **Квирк перечислен, но не действует** — предупреждение с причиной. Если гейт закрыт, так и
  сказано, и лечится открытием гейта. Если гейт открыт, а набор всё равно пуст — дело не в гейте:
  запись клиента не дошла до резолвера, чаще всего из-за второй записи с тем же `ClientId`,
  перекрывающей эту.
* **Неизвестный ключ** — старт **не** продолжается: конфигурация выбирает ключ из закрытого
  набора, и опечатка здесь означала бы тихо не применённое послабление. В сообщении перечислены
  допустимые ключи. Сравнение регистрозависимое — `Drop-Scope-On-Code-Exchange` будет отвергнут
  наравне с опечаткой.

<Note>
  Конфигурация выбирает **ключ**, а не тип и не сборку: по строке из конфигурации никогда не
  выполняется рефлексия. Право править конфигурацию не даёт исполнить свой код внутри
  identity-провайдера.
</Note>

## Реестр квирков

### `drop-scope-on-code-exchange`

**Что делает.** Убирает параметр `scope` из token-запроса с `grant_type=authorization_code`.

**Зачем.** RFC 6749 §4.1.3 в этом запросе `scope` не предусматривает, и строгая проверка отвергает
его как `invalid_request`. При обмене кода присланный `scope` всё равно ни на что не влияет —
выданный набор прав берётся из authorization code, — поэтому снятие параметра равносильно тому,
что клиент его и не отправлял. Права не расширяются.

**Кого касается.** Плагин `openid-connect-generic` для WordPress кладёт `scope` в запрос обмена
кода. Симптом — вход доходит до обмена кода и падает с ответом:

```json theme={null}
{ "error": "invalid_request", "error_description": "The 'scope' parameter is not valid in this context." }
```

В WordPress этот ответ виден не JSON-ом: плагин возвращает браузер на страницу входа сайта, а текст
ошибки кладёт в адрес — `wp-login.php?login-error=invalid_request&message=The+'scope'+parameter+is+not+valid+in+this+context.`

С включённым квирком этот же запрос проходит проверку формы, а в логе появляется запись о снятом
параметре — с `ClientId` и снятым значением. Правок на стороне сайта при этом не требуется: вход
проходит на штатном плагине, без установленных на сайт заплаток.

**Что не задето.** Путь `grant_type=refresh_token`: там `scope` разрешён RFC 6749 §6 и **сужает**
выданные права. Этот путь квирк не трогает — сужение прав законно и полезно.

**Условие снятия.** Апстрим-фикс
[`oidc-wp/openid-connect-generic#497`](https://github.com/oidc-wp/openid-connect-generic/issues/497)
выпущен и раскатан на стороне интеграторов, использующих плагин. После этого ключ убирается
вместе с обработчиком. Пока issue открыт, квирк нужен.

### `drop-resource-parameter`

**Что делает.** Снимает параметр `resource` ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707))
с запроса авторизации и с token-запроса.

**Зачем.** Ресурсы Veriqa не регистрирует, поэтому строгая проверка отвергает любое значение этого
параметра с `invalid_target`, и вход обрывается, не начавшись. Потребителя у параметра нет: без
зарегистрированных ресурсов права берутся из scope'ов, поэтому снятие даёт ровно тот же токен, что
и запрос без параметра. Квирк не выдаёт audience и не сужает его — это **не** поддержка индикаторов
ресурса.

**Кому нужен.** Плагин `auth_oidc` у Moodle сохраняет диалект Microsoft Entra ID, под который был
написан, и шлёт `resource` в каждом запросе, в том числе в generic-режиме; настройки, которая это
отключает, нет. Симптом: вход останавливается на запросе авторизации с

```json theme={null}
{ "error": "invalid_target", "error_description": "One of the specified 'resource' parameters is invalid." }
```

С включённым квирком тот же запрос проходит проверку формы, а в лог добавляется запись о снятом
параметре — с `ClientId` и снятым значением.

**Что не затрагивается.** Дефолт. Без включённого у конкретного `client_id` ключа `resource`
отвергается с `invalid_target`.

**Условие снятия.** Плагин `auth_oidc` у Moodle перестаёт слать `resource` в generic-режиме
(вне Entra). Апстрим-тикета на это нет.

## Дальше

<CardGroup cols={2}>
  <Card title="Справочник конфигурации" icon="sliders" href="/docs/ru/reference/configuration">
    Все ключи, включая клиентские.
  </Card>

  <Card title="Hardening к продакшну" icon="shield-check" href="/docs/ru/guides/hardening">
    Что проверить перед выпуском в продакшн.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.