awesome-everything EN
↑ Обратно к восхождению

Безопасность

Sender-constrained токены: DPoP и mTLS

Суть Почему bearer-токены уязвимы под XSS или supply-chain атакой, как DPoP привязывает токен к client-held ключу, и когда вместо этого использовать mTLS-bound токены — с требованиями FAPI 2.0.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 11 min

Supply-chain атака инжектирует одну строчку JavaScript в твой SPA. Она читает access-токен из памяти и exfiltrate-ит его на сервер атакующего. Токен — bearer токен: кто держит, тот и использует. Атакующий теперь имеет минуты или часы для вызова твоего API как жертва. DPoP делает украденный токен бесполезным: без соответствующего приватного ключа, который никогда не покидал клиента, атакующий не может сгенерировать валидный proof.

Проблема bearer-токена

Bearer access-токен — это ровно то, что в названии: любой держатель может его использовать. Resource server не проверяет кто представляет токен — только что токен валиден. Это дизайн.

Следствие: кража = полная компрометация на оставшееся TTL токена. Короткие TTL (5–15 мин) ограничивают window, но не устраняют его. Для банкинга, healthcare и других high-value нагрузок “украденный токен работает 15 минут” — неприемлемый риск.

DPoP — Demonstrating Proof of Possession (RFC 9449)

DPoP привязывает access-токен к key pair под контролем клиента. Клиент генерирует non-extractable key pair (WebCrypto в браузерах, OS keychain в native apps). Access-токен несёт claim cnf (confirmation) с JWK thumbprint (jkt) публичного ключа.

На каждый API-вызов клиент вычисляет DPoP proof JWT в HTTP заголовке DPoP. Payload proof содержит:

  • jti — unique ID (сервер кеширует для replay detection, типичное окно 5–10 мин)
  • htm — HTTP method (например POST)
  • htu — HTTP target URI (например https://api.bank.example/v1/transfer)
  • iat — issued-at timestamp (сервер отклоняет если вне ~60с окна)
  • ath — SHA-256 access-токена (привязывает этот proof к этому конкретному токену)

Header proof включает публичный ключ (jwk). Resource server валидирует:

  1. Подпись валидна (используя embedded jwk)
  2. ath = SHA-256 incoming bearer токена
  3. htm и htu совпадают с реальным запросом
  4. iat в допустимом окне
  5. jti не виден недавно (anti-replay)
  6. jwk хешируется в jkt в cnf claim токена

Украденный токен без приватного ключа не может пройти шаги 1 или 5. API-вызов отклонён.

Структура DPoP proof на каждый API-вызов
DPoP header (JWT подписан приватным ключом клиента)
header: { alg: ES256, jwk: {публичный ключ} }
payload: { jti: uuid, htm: “POST”, htu: “https://api.bank…/transfer”, iat: 1716230400, ath: sha256(access_token) }
cnf claim access-токена
cnf: { jkt: “base64url(SHA-256(public_key))” }
Resource server: верифицировать подпись proof, проверить ath, htm+htu, jki = jkt в токене. Украденный токен без приватного ключа проваливает шаг 1.

mTLS-bound токены (RFC 8705)

mTLS (mutual TLS) привязывает access-токен к X.509 сертификату клиента. TLS handshake сам представляет сертификат; resource server верифицирует, что TLS-presented сертификат совпадает с cnf.x5t#S256 claim в токене.

DPoP vs mTLS — практическое отличие для браузерных SPA:

  • Браузеры не раскрывают TLS client сертификаты JavaScript. mTLS требует выбора сертификата на уровне OS / браузера — непрактично для SPA, где ключ должен жить в JS.
  • DPoP использует WebCrypto API для хранения non-extractable key pair в browser JS контексте. Это работает; ключ никогда не покидает браузер, но управляется SPA кодом.
  • FAPI 2.0 (Open Banking профиль) требует sender-constrained токены через mTLS или DPoP. Для нового browser-based финтека в 2026 DPoP — дефолтный выбор. mTLS — выбор для backend service-to-service, где client сертификаты управляемы.

PAR и JAR: укрепление authorize-запроса

Pushed Authorization Requests (PAR, RFC 9126): Вместо того чтобы все параметры были в browser redirect URL, клиент POST-ит их на /par endpoint через TLS-authenticated back channel сначала. Сервер возвращает short-lived request_uri. Клиент редиректит браузер на /authorize?request_uri=... — никаких параметров в URL.

Преимущества: параметры не в browser history, referrer header или screenshots. Authorization server валидирует запрос до того, как браузер вовлечён.

JAR (JWT Authorization Requests, RFC 9101): Клиент упаковывает authorize параметры как signed JWT. Auth server валидирует подпись до обработки. В сочетании с PAR весь запрос off-URL и integrity-protected.

Оба PAR + JAR требуются FAPI 2.0. Consumer OAuth обычно скипает их; банкинг и healthcare требуют.

Викторина

Почему DPoP предпочтительнее mTLS для браузерных SPA, хотя оба FAPI 2.0 compliant?

Викторина

DPoP proof валиден, но jti был виден сервером 2 минуты назад. Что должен делать сервер?

Расставь шаги по порядку

Поставь шаги DPoP валидации resource server-а в порядке:

  1. 1 Распарсить DPoP JWT header и извлечь embedded публичный ключ (jwk)
  2. 2 Верифицировать подпись DPoP proof используя embedded публичный ключ
  3. 3 Верифицировать ath claim = SHA-256 incoming access-токена
  4. 4 Верифицировать htm и htu совпадают с реальным HTTP method и URI этого запроса
  5. 5 Верифицировать iat в допустимом time window (~60 секунд)
  6. 6 Проверить jti не виден в replay cache (5–10 минутное окно)
  7. 7 Извлечь cnf.jkt из access-токена и верифицировать jwk proof хешируется в это значение
Вспомните перед уходом
  1. 01
    Что делает DPoP proof бесполезным для украденного access-токена?
  2. 02
    Что PAR (Pushed Authorization Requests) добавляет к OAuth flow?
  3. 03
    Когда выбирать mTLS вместо DPoP для sender-constraining токенов?
Итог

Bearer-токены — legacy дефолт: любой держатель может их использовать. DPoP (RFC 9449) привязывает токен к client-held приватному ключу — каждый API-вызов требует fresh proof JWT, подписанного этим ключом, содержащего хеш access-токена, request method и target URI. Украденные токены без ключа отклоняются. mTLS-bound токены (RFC 8705) достигают той же привязки через X.509 client сертификаты. FAPI 2.0 требует одно или другое для банкинга, healthcare и регулируемых AI нагрузок. DPoP — browser выбор, потому что WebCrypto предоставляет non-extractable ключи в JavaScript; mTLS — выбор backend сервисов. PAR и JAR укрепляют сам /authorize запрос, перемещая параметры с URL.

Связанные уроки
встречается в202
Продолжить восхождение ↑OAuth в production: audience атаки, observability и реальные провалы
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.