Skip to content

JWT Token Binding — Binding Tokens to Client Sessions with cnf and tbh Claims

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about JWT Token Binding. We cover key concepts, practical examples, and best practices to help you master this topic.

JWT token binding cryptographically binds a token to the client's TLS connection or certificate, making stolen tokens useless because they can only be presented from the original client context.

What You'll Learn

  • Token binding concepts and cnf claim
  • TLS token binding with tbh hash
  • Mutual TLS (mTLS) bound tokens
  • Certificate thumbprint confirmation
  • Preventing token replay attacks

Why It Matters

Even short-lived JWTs can be stolen. Token binding ensures a stolen token cannot be used from a different device or network path. DodaTech's enterprise API uses mTLS-bound tokens, achieving zero token replay incidents since deployment.

Real-World Use

A security analyst's laptop is compromised and session tokens are exfiltrated. The attacker cannot use the tokens because they are bound to the original TLS session. The attacker's requests from a different IP and TLS context are rejected.

sequenceDiagram
    participant Client as Client (TLS A)
    participant Auth as Authorization Server
    participant API as API Server

    Client->>Auth: Request token (TLS handshake hash: tbh_A)
    Auth->>Auth: Record tbh_A in token as cnf claim
    Auth-->>Client: JWT with cnf={tbh: "hash_A"}
    Client->>API: Request with JWT (TLS A)
    API->>API: Verify tbh matches current TLS
    API-->>Client: 200 OK

    Note over Client,API: Token stolen, attacker uses TLS B

    Attacker->>API: Request with stolen JWT (TLS B)
    API->>API: tbh_B != tbh_A — token rejected
    API-->>Attacker: 401 Unauthorized

Code Examples

Example 1: Token Binding with TLS Hash

import jwt
import hashlib
from datetime import datetime, timedelta, timezone

def create_bound_token(user_id, tls_session_hash, private_key):
    """Create a JWT bound to a specific TLS session."""
    now = datetime.now(timezone.utc)
    payload = {
        'sub': user_id,
        'iss': 'https://auth.dodatech.com',
        'exp': now + timedelta(minutes=15),
        'iat': now,
        'jti': str(uuid.uuid4()),
        'cnf': {
            'tbh': tls_session_hash
        }
    }
    return jwt.encode(payload, private_key, algorithm='RS256')

def extract_tls_hash(request):
    """Extract TLS unique hash from the current request."""
    # In production, extract from SSL context
    ssl_object = request.environ.get('SSL_OBJECT')
    if ssl_object:
        session_id = ssl_object.session.session_id
        tls_hash = hashlib.sha256(session_id).hexdigest()
        print(f"TLS hash: {tls_hash[:16]}...")
        return tls_hash
    return None

# Usage during token verification
def verify_bound_token(token, current_tls_hash, public_key):
    try:
        payload = jwt.decode(token, public_key, algorithms=['RS256'])
        cnf = payload.get('cnf', {})

        if cnf.get('tbh') != current_tls_hash:
            print("Token binding mismatch — possible token theft")
            return None

        return payload
    except Exception as e:
        print(f"Verification failed: {e}")
        return None

Example 2: mTLS Certificate Binding

import jwt
import hashlib
from cryptography.x509 import load_pem_x509_certificate

def bind_to_certificate(user_id, client_cert_pem, private_key):
    """Bind token to client's TLS certificate thumbprint."""
    cert = load_pem_x509_certificate(client_cert_pem.encode())
    cert_thumbprint = hashlib.sha256(cert.public_bytes_raw()).hexdigest()

    now = datetime.now(timezone.utc)
    payload = {
        'sub': user_id,
        'exp': now + timedelta(minutes=15),
        'iat': now,
        'jti': str(uuid.uuid4()),
        'cnf': {
            'x5t#S256': cert_thumbprint
        }
    }
    token = jwt.encode(payload, private_key, algorithm='RS256')
    print(f"Token bound to certificate: {cert_thumbprint[:16]}...")
    return token

def verify_certificate_binding(token, client_cert_pem, public_key):
    """Verify that the token is bound to the presenting certificate."""
    try:
        payload = jwt.decode(token, public_key, algorithms=['RS256'])
        cnf = payload.get('cnf', {})

        cert = load_pem_x509_certificate(client_cert_pem.encode())
        presented_thumbprint = hashlib.sha256(cert.public_bytes_raw()).hexdigest()
        bound_thumbprint = cnf.get('x5t#S256')

        if presented_thumbprint != bound_thumbprint:
            print(f"Certificate mismatch")
            print(f"  Bound to: {bound_thumbprint[:16]}...")
            print(f"  Presented: {presented_thumbprint[:16]}...")
            return None

        print("Certificate binding verified")
        return payload
    except Exception as e:
        print(f"Verification failed: {e}")
        return None

Example 3: Binding Validation Middleware

from flask import Flask, request, jsonify, g
from functools import wraps

app = Flask(__name__)

def require_token_binding(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization', '').replace('Bearer ', '')
        if not token:
            return jsonify({'error': 'No token'}), 401

        # Get current TLS hash
        tls_hash = extract_tls_hash(request)
        if not tls_hash:
            return jsonify({'error': 'TLS binding unavailable'}), 400

        # Verify token with binding
        payload = verify_bound_token(token, tls_hash, PUBLIC_KEY)
        if not payload:
            return jsonify({'error': 'Token binding validation failed'}, 401)

        g.user = payload['sub']
        return f(*args, **kwargs)
    return decorated

@app.route('/api/secure-data')
@require_token_binding
def secure_data():
    return jsonify({
        'message': 'Access granted with bound token',
        'user': g.user
    })

Common Mistakes

1. Binding Without Validation

Issuing bound tokens is useless without verifying the binding on every request.

2. Using Weak Binding Mechanisms

TLS session IDs can change. Use TLS unique IDs or certificate thumbprints for reliable binding.

3. Not Handling Binding Failures Gracefully

Return a clear error message so legitimate clients can re-authenticate.

4. Binding to Ephemeral Values

Don't bind to values that change frequently (e.g., IP addresses in cloud environments).

5. Ignoring Clock Skew in Validation

TLS binding verification must tolerate small clock differences between services.

Practice Questions

  1. What is the cnf claim used for?
  2. How does token binding prevent replay attacks?
  3. What is the difference between tbh and x5t#S256 binding?
  4. Why is IP address binding not recommended?
  5. How do you recover from a binding verification failure?

Answers:

  1. The cnf (confirmation) claim carries proof that the presenter possesses a certain key or TLS context.
  2. Even if the token is stolen, it cannot be used from a different TLS connection or without the correct client certificate.
  3. tbh binds to the TLS session hash; x5t#S256 binds to the client certificate's SHA-256 thumbprint.
  4. IP addresses change in cloud/mobile environments, causing legitimate clients to fail binding checks.
  5. Return a 401 with error=token_binding_failed and error_description explaining the user should re-authenticate.

Challenge: Implement mTLS-bound JWT tokens for a confidential API. Generate tokens bound to client certificates and verify the binding on every request. Test that a token from one client is rejected when presented by another.

FAQ

Does token binding work with load balancers?

: Yes, but ensure TLS termination preserves the original TLS hash or uses Proxy Protocol to pass the original connection info.

Is token binding supported by all JWT libraries?

: The cnf claim is defined in RFC 7800. Most JWT libraries support custom claims, so you can implement binding manually.

Can I combine token binding with refresh tokens?

: Yes. Bind refresh tokens to client certificates for defense in depth.

What happens during TLS session resumption?

: The TLS hash changes. Use certificate thumbprint binding instead of TLS hash for long-lived sessions.

Does token binding add latency?

: Minimal — cryptographic hashing is fast. The overhead is negligible compared to TLS handshake costs.

What's Next

Apply token binding in your {{< ilink "JWT" "JWT Authentication Service" }}, and review {{< ilink "JWT" "JWT Best Practices" }} for comprehensive security hardening.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro