demo · v142

Protocol Handshake

Step through the actual DBSC HTTP/JWS dance from the IETF draft: server says Sec-Session-Registration, client generates a TPM-backed keypair and POSTs a signed registration JWT, server returns a short-lived cookie plus the refresh URL, browser auto-renews by signing the server's nonce. This is the bit that's hard to picture from prose — the steps below run the exchange against a mocked server in your tab.

Chrome 142 ships DBSC for Google's first-party origins. For other origins enable chrome://flags/#enable-device-bound-session-credentials and serve over HTTPS. This page runs a pure-JS mock of the protocol so the handshake is observable without a real server or TPM.
  1. server → client Sign-in response includes header

    The server sets a short-lived auth cookie and tells the browser to start a device-bound session.

    HTTP/2 200 OK
    Set-Cookie: auth_session=abc…; Max-Age=600; Secure; HttpOnly
    Sec-Session-Registration: (RS256 ES256);path="/StartSession";challenge="cha_001"
  2. browser → TPM Generate device-bound keypair

    Chrome asks the OS to create a non-extractable keypair inside the TPM / Secure Enclave. The private key never leaves hardware.

    // pseudo-internal
    const kp = await deviceTpm.generate({ alg: "ES256", extractable: false });
  3. client → server POST signed registration JWT to /StartSession

    Body is a JWS proving possession of the new private key and echoing the server's challenge.

    POST /StartSession
    Content-Type: application/jwt
    
    eyJ0eXAiOiJzZXNz…Y2hhX18wMDEi  ← header.payload.signature
    { "iat": 1730…, "aud": "https://app.example", "jti": "cha_001",
      "key": { "kty":"EC","crv":"P-256","x":"…","y":"…" } }
  4. server → client Session config + first refresh cookie

    Server confirms the binding, returns the JSON session config and a refresh cookie tagged with the session ID.

    HTTP/2 200 OK
    Set-Cookie: auth_session=xyz…; Max-Age=600
    Content-Type: application/json
    
    { "session_identifier":"sess_42",
      "refresh_url":"/RefreshSession",
      "scope":{"include_site":true},
      "credentials":[{"name":"auth_session","attributes":"Secure;HttpOnly"}] }
  5. client → server 10 minutes later: auth_session expired, auto-refresh

    Browser intercepts the would-be request, fetches /RefreshSession with a fresh signed JWT proving the same TPM key.

    POST /RefreshSession
    Sec-Session-Id: sess_42
    Content-Type: application/jwt
    
    eyJhbGciOiJFUzI1Ni…  ← signed with the same device key
  6. server → client Refresh succeeds, new short-lived cookie issued

    Server validates the JWS against the registered public key, mints a fresh auth_session cookie. An attacker who exfiltrated the old cookie can't refresh — they don't have the TPM key.

    HTTP/2 200 OK
    Set-Cookie: auth_session=def…; Max-Age=600; Secure; HttpOnly
DeviceBoundSession in navigator
current step
idle

DBSC's whole value is steps 4–5: the server bound the auth cookie to a TPM key on step 2, so any exfiltrated cookie can only buy the attacker minutes before the next refresh fails on their machine.

see also