Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DBSCを実装してみて得た気づき

 DBSCを実装してみて得た気づき

Windows版Chrome 145で導入されたDBSC(Device Bound Session Credentials)は、端末内の秘密鍵を利用して短命なCookieを更新し、Cookieが盗まれてもログイン状態を維持しにくくする仕組みです。

本資料では、Googleログインを利用する管理画面へのDBSC導入時に遭遇した「ログイン後のcallback GETには届くSameSite=LaxのセッションCookieが、ChromeによるDBSC登録リクエストには届かない」という問題を題材に、DBSCの基本的な仕組みとCookieの送信条件について解説します。

同じアプリへのリクエストでもCookieの送信条件が変わる理由と、DBSC登録を開始する応答を変更することでSameSite=Laxを維持したまま解決した方法を、実際のリクエストフローに沿って紹介します。

Avatar for Yudai Tanaka

Yudai Tanaka

October 04, 2026

More Decks by Yudai Tanaka

Other Decks in Technology

Transcript

  1. 自己紹介 田中 湧大 株式会社 PR TIMES / ソフトウェアエンジニア 主にバックエンドを担当しています SF6

    MR1600から上にいけません。旅行が好きです。 @Romira915 PR TIMES Romira915
  2. DBSC (Device Bound Session Credentials) とは • Chrome 145(Windows)で導入された ログイン後のセッションを守る技術

    • 盗まれたセッションCookieは、別の端末でも使われ得る • DBSCは短命Cookieを自動で更新 更新には端末内の取り出せない秘密鍵が必要 • 盗まれた Cookieだけでは、更新を続けられない PR TIMES
  3. Registration 今回の実装では、セッション Cookieで公開鍵をユーザーに紐付ける サーバー Chrome POST /path/to/login ログイン情報を送信 ログイン成功レスポンスのヘッダー Set-Cookie:

    session=...; SameSite=Lax Secure-Session-Registration: (ES256) path="/path/to/registration"; challenge="…" POST /path/to/registration Secure-Session-Response: "JWT" 200 設定JSON + DBSC用Cookie Set-Cookie: __Secure-dbsc=...; Max-Age=600 PR TIMES DBSC登録リクエスト
  4. Registration 今回の実装では、セッション Cookieで公開鍵をユーザーに紐付ける サーバー Chrome POST /path/to/login ログイン情報を送信 ログイン成功レスポンスのヘッダー Set-Cookie:

    session=...; SameSite=Lax Secure-Session-Registration: (ES256) path="/path/to/registration"; challenge="…" POST /path/to/registration Secure-Session-Response: "JWT" 200 設定JSON + DBSC用Cookie Set-Cookie: __Secure-dbsc=...; Max-Age=600 PR TIMES DBSC登録リクエスト
  5. Registration 今回の実装では、セッション Cookieで公開鍵をユーザーに紐付ける サーバー Chrome POST /path/to/login ログイン情報を送信 ログイン成功レスポンスのヘッダー Set-Cookie:

    session=...; SameSite=Lax Secure-Session-Registration: (ES256) path="/path/to/registration"; challenge="…" POST /path/to/registration DBSC登録リクエスト ←payloadにchallengeと 公開鍵が入っている 200 設定JSON + DBSC用Cookie Secure-Session-Response: "JWT" Set-Cookie: __Secure-dbsc=...; Max-Age=600 PR TIMES
  6. Registration 今回の実装では、セッション Cookieで公開鍵をユーザーに紐付ける サーバー Chrome POST /path/to/login ログイン情報を送信 ログイン成功レスポンスのヘッダー Set-Cookie:

    session=...; SameSite=Lax Secure-Session-Registration: (ES256) path="/path/to/registration"; challenge="…" POST /path/to/registration Secure-Session-Response: "JWT" 200 設定JSON + DBSC用Cookie Set-Cookie: __Secure-dbsc=...; Max-Age=600 PR TIMES DBSC登録リクエスト
  7. DBSC登録リクエストに Cookieが届かない Google OAuthでログインする管理画面 Googleログイン後のcallbackリクエスト(例: GET /path/to/oauth/callback) リクエスト: Cookie: id=...(SameSite=Lax)

    レスポンスヘッダー: Secure-Session-Registration: ... ↓ ChromeがDBSC登録リクエストを送信 DBSC登録リクエスト( 例:POST /path/to/registration) SameSite=None のCookie 届いた セッションCookie(Lax) 届かない なぜ Lax のセッション Cookieが届かない? PR TIMES
  8. callback GETの応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 この通信の起点:Google (GET /path/to/oauth/callback) ↑ 通信の起点が異なるが

    callback GET の応答 レスポンスヘッダー: Secure-Session-Registration その後、Chromeが裏で自動送信する登録リクエスト (POST /path/to/registration) この通信の起点:Google PR TIMES Googleログイン後、自サイトの画面 に戻るGETリクエストなので SameSite=Laxは送信される
  9. callback GETの応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 この通信の起点:Google (GET /path/to/oauth/callback) ↑ 通信の起点が異なるが

    callback GET の応答 レスポンスヘッダー: Secure-Session-Registration Googleログイン後、自サイトの画面 に戻るGETリクエストなので SameSite=Laxは送信される その後、Chromeが裏で自動送信する登録リクエスト (POST /path/to/registration) この通信の起点:Google PR TIMES ←続くDBSC登録もGoogle起点の扱いを引き継ぐ
  10. callback GETの応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 この通信の起点:Google (GET /path/to/oauth/callback) ↑ 通信の起点が異なるが

    callback GET の応答 レスポンスヘッダー: Secure-Session-Registration その後、Chromeが裏で自動送信する登録リクエスト Googleログイン後、自サイトの画面 に戻るGETリクエストなので SameSite=Laxは送信される 通信の起点が異なりfetchリクエスト ← なのでLaxは送信されない (POST /path/to/registration) この通信の起点:Google ←続くDBSC登録もGoogle起点の扱いを引き継ぐ Laxのセッション Cookieが届かない PR TIMES
  11. 登録開始ページの GET応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 (GET /path/to/oauth/callback) location.href → DBSC登録開始ページ

    (GET /path/to/registration-init) DBSC登録開始ページ GET の応答 レスポンスヘッダー: Secure-Session-Registration DBSC登録リクエスト: POST /path/to/registration PR TIMES
  12. 登録開始ページの GET応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 (GET /path/to/oauth/callback) location.href → DBSC登録開始ページ

    (GET /path/to/registration-init) DBSC登録開始ページ GET の応答 レスポンスヘッダー: Secure-Session-Registration DBSC登録リクエスト: POST /path/to/registration PR TIMES ←200 HTML レスポンスを返す
  13. 登録開始ページの GET応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 ←200 HTML レスポンスを返す (GET /path/to/oauth/callback)

    location.href → DBSC登録開始ページ ←そのページのJavaScriptから遷移 (GET /path/to/registration-init) DBSC登録開始ページ GET の応答 レスポンスヘッダー: Secure-Session-Registration DBSC登録リクエスト: POST /path/to/registration PR TIMES
  14. 登録開始ページの GET応答で登録開始した場合 Googleログイン画面 → 自サイトのログイン完了処理 ←200 HTML レスポンスを返す (GET /path/to/oauth/callback)

    location.href → DBSC登録開始ページ ←そのページのJavaScriptから遷移 (GET /path/to/registration-init) DBSC登録開始ページ GET の応答 レスポンスヘッダー: Secure-Session-Registration DBSC登録リクエスト: POST /path/to/registration ←通信の起点が自サイトにリセットされる Laxのセッション Cookieが届き、 DBSCの登録処理を開始できる! 通信の起点:自サイト PR TIMES
  15. 登録開始ページの GET応答で登録開始 Google → GET /path/to/oauth/callback ←一旦 200 HTML レスポンスを返す

    DBSC登録リクエストから セッションと公開鍵を 紐づけることができた 🚀 location.href → GET /path/to/registration-init ←そのページのJavaScriptから遷移 DBSC登録開始ページ GET の応答 レスポンスヘッダー: Secure-Session-Registration DBSC登録リクエスト: POST /path/to/registration ←initiatorが自サイトにリセットされる Laxのセッション Cookieが届く 通信の起点:自サイト PR TIMES 13
  16. References DBSC仕様(Registration / Refresh) w3c.github.io DBSC Explainer(Privacy considerations) github.com/w3c/webappsec-dbsc W3C

    Issue #106(SameSite=Laxの報告) github.com/w3c/webappsec-dbsc Cookie仕様(SameSite) httpwg.org Chromium実装(registration_fetcher.cc) chromium.googlesource.com Chrome 145(Windows)公式告知 developer.chrome.com PR TIMES
  17. Registration サーバーが返す設定 JSON { "session_identifier": "dbsc-session-1", "refresh_url": "/path/to/refresh" , "scope":

    { "include_site": false }, "credentials": [{ "type": "cookie", "name": "__Secure-dbsc" , "attributes": "Path=/; Secure; HttpOnly; SameSite=Lax" }] } PR TIMES
  18. Refresh 短命Cookieの更新 サーバー Chrome POST /path/to/refresh Sec-Secure-Session-Id: "dbsc-session-1" 403 チャレンジを返す

    Secure-Session-Challenge: "refresh-nonce-1";id="dbsc-session-1" POST /path/to/refresh Sec-Secure-Session-Id: "dbsc-session-1" Secure-Session-Response: "JWT"(チャレンジを含むJWTを秘密鍵で署名) 200 新しいDBSC用Cookie Set-Cookie: __Secure-dbsc=...; Max-Age=600 PR TIMES
  19. GETのレスポンスで DBSC登録を開始 other.test → fetch GET → app.example このGETにはSameSite=LaxのCookieが届かない GETのレスポンスヘッダー:

    Secure-Session-Registration ↓ Chromeが自動で送信 DBSC登録リクエスト: POST /path/to/registration PR TIMES
  20. 別サイトからの fetchを起点にした場合 other.test → fetch GET → app.example このGETにはSameSite=LaxのCookieが届かない GETのレスポンスヘッダー:

    Secure-Session-Registration ↓ Chromeが自動で送信 DBSC登録リクエスト: POST /path/to/registration 元のinitiator = other.test 検証では Lax Cookieは届かなかった PR TIMES