session-lock is a device-token binding scheme and JS library that reduces the risk of session hijacking / token replay attacks by binding the validity of a user's session token to their browser or device.

The library can be used with both JWTs and serialized cookies, though working with JWTs is easier with this library thanks to JWTs' ability to store data within their payloads. The general concept and flow can be implemented in mobile apps using CryptoKit and KeyStore.

Background

Regardless of how strong a service's authentication mechanisms may be, if an attacker steals a user's session token, they can access protected resources belonging to the user much like they would if they had access to the user's authentication credentials. With the use of OS-level malware, malicious browser extensions, or traffic sniffing, an attacker can steal users' session tokens directly from their browsers or from the network, then use those tokens to authorize their own sessions.

Below is a manual demonstration of how session hijacking works. We're taking the the token from a logged-in session and using it on another browser. A bad actor would do this programmatically with a browser extension or malware, but the principle is the same.

Existing RFCs have attempted to address the token hijacking threat. A draft RFC for demonstrating proof-of-possession (DPoP) proposes a similar mechanism to session-lock, but it is primarily applicable to OAuth systems and may be complex to implement. Google Chrome's Device Bound Session Credentials (DBSC) proposal is promising, though cross-browser support and compatibility with native mobile apps remains unclear at this time.

Try session-lock

session-lock can be demonstrated on this site here. Try to take the JWT from LocalStorage and re-use it in another browser or another tab. You will see that the JWT is rejected. This is because the JWT is bound to the browser in which it was issued.

The demo showcases a Next.js app using the session-lock library to secure a protected route, accessible only to users with a valid session-lock token.

Try session-lock in your own project

session-lock is available for web and Node.js applications as a proof-of-concept library through npm. Source code and documentation for the library and the demo site can be found on GitHub.

How session-lock works

A regular JWT: {header}.{payload}.{server sig}

A session-lock token: {header}.{payload w/ client pubKey}.{server sig}.{timestamp}.{client sig}

Note: S-L in the diagram below stands for session-lock. This diagram is JWT-specific in terms of where the client public key is stored and how it's retrieved, but the same principles apply to serial session tokens / cookies as well. In summary, whereas regular JWTs include a signature generated by the issuing server, session-lock adds a signature generated by the client to which it was issued, with a timestamp added to the mix to make the signature resistant to replay. The client signature is made reliable with the use of unextractable private keys stored in the browser's IndexedDB. The same timestamp+signature can be applied to serialized session tokens / cookies, with the difference being that the client public key is stored in a cookie store on the server rather than embedded within the token itself.

The system may be vulnerable to client-side javascript tampering as well as somehow extracting "unextractable" SubtleCrypto private keys, but it should provide a strong layer of protection against less sophisticated attacks.