TryMellon
Navigation

Session binding (DBSC)

Bind session tokens to a device-generated key. Stolen tokens stop working the moment they leave the original device — the fix for session-stealing malware that 2FA cannot catch.

Session binding (DBSC)

Phished credentials are a solved problem — passkeys won’t sign a fake origin. But what about the session after the user logs in? A malware-stolen bearer token can impersonate the user for hours, from anywhere, and no authentication step will catch it.

TryMellon implements Device-Bound Session Credentials (DBSC, the W3C draft). On login, the browser generates a TPM-backed ECDSA P-256 keypair. Every session refresh requires a Proof-of-Possession (PoP) JWT signed with that key. The session token becomes useless the moment it leaves the device it was issued on.

Google enabled DBSC in Chrome 113+ as a countermeasure to cookie-theft malware. TryMellon makes it a first-class session primitive — no extension, no bespoke code in your app.


The attack DBSC blocks

Without DBSC                        With DBSC
─────────────                       ─────────
1. User logs in                     1. User logs in + device registers TPM key
2. Bearer token set                 2. Bearer token set + sec_session_id recorded
3. Malware exfiltrates cookie       3. Malware exfiltrates cookie
4. Attacker reuses cookie from      4. Attacker reuses cookie from attacker machine
   attacker machine — ACCEPTED         — attacker has no TPM private key
5. Full account takeover            5. Next refresh fails PoP check → session revoked

Multi-factor, device fingerprinting, and IP reputation all happen at auth time. DBSC happens at every request that refreshes the session. It turns a stolen token from a persistent breach into a seconds-long one.


When to use it

Product shapeWhy DBSC pays off
CeFi / crypto exchangesToken theft is the dominant residual risk after passkeys. DBSC closes it.
Consumer fintech with web accessMalware targeting browser cookies is a $Bn industry. This is the specific fix.
Enterprise admin consolesSession hijacking from developer machines is the top IR case in the enterprise segment.
High-assurance B2BDBSC is a clear SOC2 / ISO differentiator vs Auth0/Okta defaults.

If your users only reach your app via native mobile (no browser), DBSC buys less — native apps have their own keystore primitives. Most deployments have some browser traffic.


How it works

Browser                              Your backend                   TryMellon API
  │                                        │                              │
  │── sign in (passkey / SDK) ────────────>│                              │
  │                                        │── validate session ─────────>│
  │                                        │<── { session_token, sec_session_id }
  │<── Sec-Session-Registration header ────│                              │
  │                                        │                              │
  │  generate ECDSA P-256 keypair          │                              │
  │  private key lives in TPM / Secure     │                              │
  │  Enclave — cannot be exported          │                              │
  │                                        │                              │
  │── POST /v1/session-binding/register ──────────────────────────────────>│
  │   { public_key_jwk, challenge_sig }                                    │
  │<── { sec_session_id, refresh_interval_ms } ────────────────────────────│
  │                                                                       │
  │  every refresh_interval_ms:                                           │
  │  sign a PoP JWT with the private key                                  │
  │                                                                       │
  │── POST /v1/session-binding/verify ────────────────────────────────────>│
  │   { sec_session_id, pop_jwt }                                          │
  │<── { ok: true } or { ok: false, revoke }                              │
  1. On login, the browser receives a Sec-Session-Registration challenge.
  2. The browser generates a TPM-backed ECDSA P-256 keypair. The private key is non-exportable.
  3. The browser posts the public JWK and a signature over the challenge to /v1/session-binding/register. TryMellon stores the public key tied to the session.
  4. At a configured interval (default 10 minutes), the browser generates a fresh PoP JWT signed with the device key and sends it to /v1/session-binding/verify.
  5. A verification failure marks the session for revocation — your SDK receives session.revoked and forces re-authentication.

Quick start

DBSC is enabled per deployment via the DBSC_ENABLED flag. When enabled, the SDK handles the browser side transparently — you only need to allow the register/verify round-trips to reach TryMellon.

Server-to-server: register the session

POST https://api.trymellonauth.com/v1/session-binding/register
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/json

{
  "public_key_jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "base64url-x-coordinate",
    "y": "base64url-y-coordinate"
  },
  "algorithm": "ES256",
  "challenge_signature": "base64url-signature-over-challenge",
  "origin": "https://your-app.com"
}

Response — 201 Created:

{
  "ok": true,
  "data": {
    "sec_session_id": "sec_01HXYZ…",
    "expires_at": "2026-04-17T11:00:00.000Z",
    "refresh_interval_ms": 600000
  }
}

Server-to-server: verify a PoP JWT

POST https://api.trymellonauth.com/v1/session-binding/verify
Authorization: Basic <base64(client_id:client_secret)>
Content-Type: application/json

{
  "sec_session_id": "sec_01HXYZ…",
  "pop_jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0Iiwiandr…"
}

Response — success:

{
  "ok": true,
  "data": {
    "verified": true,
    "sec_session_id": "sec_…",
    "next_refresh_in_ms": 600000
  }
}

Response — revoked:

{
  "ok": false,
  "error": { "code": "dbsc_signature_invalid", "message": "PoP JWT signature verification failed" }
}

A failure here must be treated as a session compromise: revoke, force re-auth, and log.


API reference

POST /v1/session-binding/register

  • Auth: Basic Auth (S2S).

  • Request body:

    FieldTypeRequiredDescription
    public_key_jwkJWK objectyesECDSA P-256 public key.
    algorithm"ES256"yesOnly ES256 is accepted today.
    challenge_signaturestring (base64url)yesSignature over the server-issued challenge.
    originstring (URL)yesOrigin from which registration was initiated. Must match rpId.
  • Response: { sec_session_id, expires_at, refresh_interval_ms }.

  • TTL: controlled by the DBSC_SESSION_TTL_SECONDS env (default 3600).

POST /v1/session-binding/verify

  • Auth: Basic Auth (S2S).

  • Request body:

    FieldTypeRequiredDescription
    sec_session_idstring (UUID)yesReturned by /register.
    pop_jwtstringyesFresh PoP JWT signed with the device key.
  • Response: { verified, sec_session_id, next_refresh_in_ms } on success.


Browser support

DBSC requires a modern browser with TPM access:

BrowserStatus
Chrome 113+ (desktop)✅ Full support (origin trial → stable)
Edge 113+✅ Via Chromium
Safari⚠️ Not implemented as of 2026-04 — SDK falls back to bearer-only sessions
Firefox⚠️ Behind flag
Mobile Chrome / Android WebView
Mobile Safari⚠️ See above

Unsupported browsers continue to work with standard bearer-token sessions. Enforcement of DBSC per user-agent is a dashboard policy.


Error codes

HTTPCodeCause
400dbsc_invalid_jwkpublic_key_jwk is malformed or missing coordinates.
400dbsc_algorithm_unsupportedalgorithm is not "ES256".
400dbsc_origin_mismatchorigin does not match the application’s rpId.
401dbsc_signature_invalidChallenge or PoP JWT signature failed verification.
404dbsc_session_not_foundsec_session_id unknown or expired.
410dbsc_session_revokedSession was revoked after a prior failed verification.
501not_implementedDBSC_ENABLED is false in this deployment.

Security guarantees

  • Private key is non-exportable. Chrome binds to the OS TPM or Secure Enclave. Extraction requires physical possession and vendor-level compromise.
  • Every refresh is fresh. PoP JWTs include a jti (unique per refresh) and iat/nbf claims. Replay detection is built in.
  • Atomic revocation. A failed verification marks the session for revocation via Redis SET NX. Subsequent refreshes return 410 Gone.
  • Origin-bound. The origin claim in both registration and PoP must match your configured rpId. A malicious extension on a different origin cannot produce a valid PoP.
  • Separate from authentication. DBSC is orthogonal to passkey login — you get phishing-resistant login and session integrity as independent layers.

Operational guidance

  • Pair with passkeys. DBSC protects the session; passkeys protect the login. Each layer fails open without the other — deploy both.
  • Monitor dbsc_signature_invalid. Sustained failures for a single user can indicate malware on their device. Add it to your SIEM feed.
  • Tune refresh_interval_ms. The default 10 minutes balances UX and security. Treasury or admin contexts can safely go to 2–5 minutes.
  • Log sec_session_id. Include it alongside user IDs in your app-side logs — it’s your index into session lineage if you ever need to audit.