Introduction
Recently, a security engineer @nishikawaakira introduced an approach that uses JA4 TLS fingerprints to help prevent session cookie theft. The proof of concept (PoC) associates a fingerprint with a session at login time to make stolen cookies more difficult to reuse.
Consider an architecture in which the application and API server are separate: the application manages session cookies, while access tokens are used for requests to the API server. Binding the session cookie to a JA4 fingerprint can raise the barrier to reusing a stolen cookie. However, distributing access policy decisions and enforcement across both the application server and the API server can make complex, dynamic policy logic and logging harder to manage.
This article presents a PoC that binds a CloudFront JA4 fingerprint to an access token as a custom claim, allowing the API server to determine the path through which the access token is being used. This keeps servers that mediate between session cookies and tokens—such as an application server or BFF—as thin wrappers, while centralizing access policy decisions and enforcement.
The PoC is available in the following repository:
https://github.com/manaty226/cloudfront-ja4-binding-token
System Architecture
This PoC uses JA4 fingerprints collected by CloudFront. At login time, the Cognito Pre Token Generation Lambda trigger adds a hash of the JA4 fingerprint to the access token as a custom claim. The fingerprint is hashed to make it more difficult for an attacker to infer and impersonate the underlying class of TLS stack.
The BFF endpoint, implemented with API Gateway, retrieves the corresponding access token from the session cookie. It then sends both the JA4 fingerprint observed for the current request and the access token to the API endpoint. The API endpoint compares the hash of the fingerprint in the access token's custom claim with the hash of the fingerprint observed for the request, then allows or denies access based on the result.
As noted in the article linked in the introduction, a JA4 fingerprint can change even when the same browser is used. A production implementation would therefore need to consider how fingerprints should be compared, how claim values should be derived accordingly, and how users or operators should be notified when fingerprints differ.
Implementation Tip
The Cognito Pre Token Generation Lambda trigger cannot access request context such as the headers or body of the token request. As a workaround, this PoC forces the JA4 fingerprint into a user attribute. A production system would need a mechanism that captures the JA4 fingerprint for each authorization request session and includes it in the access token claims.
Experimental Results
After opening the login endpoint in a browser and entering a login ID and password, the page shown below appears. First, click the button that accesses the BFF endpoint from the browser. Because the BFF endpoint sends the JA4 fingerprint from the same browser used at login together with the access token to the API endpoint, the request succeeds.
Next, use curl to access the BFF endpoint while presenting the session cookie:
curl -s -b "session_id=<YOUR_SESSION_ID>" https://<YOUR_BFF_ENDPOINT>/bff/private
The following response is returned. Because the browser and curl send different JA4 fingerprints, the fingerprint observed for this request does not match the fingerprint in the access token's custom claim, and the API server denies access.
{
"error": "JA4 fingerprint binding check failed",
"reason": "JA4 fingerprint mismatch",
"token_ja4_hash": "...",
"observed_ja4_hash": "..."
}
Conclusion
OAuth extensions such as RFC 9449, DPoP (Demonstrating Proof of Possession), and RFC 8705, OAuth Mutual-TLS Client Authentication and Certificate-Bound Access Tokens, define ways to strictly verify the sender of an access token and constrain its use. However, applying these specifications is often difficult because they require browsers to manage private keys or require client certificates to be installed on devices. As a result, access tokens are commonly managed through a BFF, which in turn makes various measures necessary to protect session cookies.
The approach described here does not strictly constrain the token sender. An attacker can impersonate the fingerprint by using the same TLS stack. Conversely, as the article referenced in the introduction notes, a JA4 fingerprint can change even within the same browser, making false positives another concern.
Even with these limitations, treating the request path as a constraint on access-token use makes it possible to centralize access policies. This avoids distributing policy logic and logs across the system and makes it easier to manage a consistent access policy. Because the fingerprint check can be performed as part of API authorization at the resource server, a fingerprint mismatch could also trigger reauthentication through a standard mechanism such as the RFC 9470 OAuth 2.0 Step Up Authentication Challenge Protocol.
For these reasons, constraining the request path within the API authorization layer may be an approach worth exploring.


Top comments (0)