JWT Token Binding — Binding Tokens to Client Sessions with cnf and tbh Claims
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
- What is the cnf claim used for?
- How does token binding prevent replay attacks?
- What is the difference between tbh and x5t#S256 binding?
- Why is IP address binding not recommended?
- How do you recover from a binding verification failure?
Answers:
- The
cnf(confirmation) claim carries proof that the presenter possesses a certain key or TLS context. - Even if the token is stolen, it cannot be used from a different TLS connection or without the correct client certificate.
tbhbinds to the TLS session hash;x5t#S256binds to the client certificate's SHA-256 thumbprint.- IP addresses change in cloud/mobile environments, causing legitimate clients to fail binding checks.
- Return a 401 with
error=token_binding_failedanderror_descriptionexplaining 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
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