Skip to content

Commit b6c901e

Browse files
feat: add include_jti flag to control jti claim inclusion in DPoP proof generation
1 parent 41fb87a commit b6c901e

2 files changed

Lines changed: 11 additions & 16 deletions

File tree

packages/auth0_api_python/src/auth0_api_python/token_utils.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ async def generate_dpop_proof(
106106
iat: bool = True,
107107
claims: Optional[dict[str, Any]] = None,
108108
header_overrides: Optional[dict[str, Any]] = None,
109-
iat_time: Optional[int] = None
109+
iat_time: Optional[int] = None,
110+
include_jti: bool = True
110111
) -> str:
111112
"""
112113
Generates a real ES256-signed DPoP proof JWT using the EC private key above.
@@ -120,6 +121,7 @@ async def generate_dpop_proof(
120121
claims: Additional custom claims to merge into the proof.
121122
header_overrides: Override header parameters (e.g., for testing invalid headers).
122123
iat_time: Fixed time for iat claim (for testing). If None, uses current time.
124+
include_jti: Whether to include the 'jti' claim. If False, jti is completely omitted.
123125
124126
Returns:
125127
An ES256-signed DPoP proof JWT string.
@@ -140,10 +142,11 @@ async def generate_dpop_proof(
140142
if iat:
141143
proof_claims["iat"] = iat_time if iat_time is not None else int(time.time())
142144

143-
if jti is not None:
144-
proof_claims["jti"] = jti
145-
else:
146-
proof_claims["jti"] = str(uuid.uuid4())
145+
if include_jti:
146+
if jti is not None:
147+
proof_claims["jti"] = jti
148+
else:
149+
proof_claims["jti"] = str(uuid.uuid4())
147150

148151
proof_claims["htm"] = http_method
149152
proof_claims["htu"] = normalize_url_for_htu(http_url)

packages/auth0_api_python/tests/test_api_client.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -970,22 +970,14 @@ async def test_verify_dpop_proof_with_missing_jti():
970970
"""Test verify_dpop_proof with missing jti claim."""
971971
access_token = "test_token"
972972

973+
# Generate DPoP proof WITHOUT jti claim from the start
973974
dpop_proof = await generate_dpop_proof(
974975
access_token=access_token,
975976
http_method="GET",
976977
http_url="https://api.example.com/resource",
977-
jti=None,
978-
claims={"jti": None}
978+
include_jti=False # Completely omit jti claim
979979
)
980980

981-
parts = dpop_proof.split('.')
982-
if len(parts) == 3:
983-
header, payload, signature = parts
984-
decoded_payload = json.loads(base64.urlsafe_b64decode(payload + '=' * (4 - len(payload) % 4)).decode('utf-8'))
985-
del decoded_payload['jti']
986-
modified_payload = base64.urlsafe_b64encode(json.dumps(decoded_payload).encode('utf-8')).decode('utf-8').rstrip('=')
987-
dpop_proof = f"{header}.{modified_payload}.{signature}"
988-
989981
api_client = ApiClient(ApiClientOptions(domain="auth0.local", audience="my-audience"))
990982
with pytest.raises(InvalidDpopProofError) as err:
991983
await api_client.verify_dpop_proof(
@@ -994,7 +986,7 @@ async def test_verify_dpop_proof_with_missing_jti():
994986
http_method="GET",
995987
http_url="https://api.example.com/resource"
996988
)
997-
assert "signature verification failed" in str(err.value).lower()
989+
assert "missing required claim: jti" in str(err.value).lower()
998990

999991
@pytest.mark.asyncio
1000992
async def test_verify_dpop_proof_fail_htm_mismatch():

0 commit comments

Comments
 (0)