Recently, I was working on an application where a new requirement came up: users should be able to see all the devices they are logged in from and log out from a specific device if needed.
This was something I hadn’t implemented before, which made it an interesting challenge.
At that point, the application was using JWT-based authentication. After a successful login, a JWT was generated and stored on client's side, and every request sent this token back to the server for authentication.
This approach worked well and had a major advantage: the server didn’t need to store any authentication state. That made the system simple, scalable, and easy to reason about.
But it also came with a limitation.
The Limitation of Stateless JWTs
JWTs are stateless by design. Once a token is issued, the server has no native way to know how many devices a user is logged in from, which device a request is coming from, or whether access from a specific device should be revoked.
As soon as features like device management or remote logout are needed, this lack of state becomes a real problem.
At that point, it became clear that the backend needed some form of state.
Introducing Session Storage
The solution I ended up with was introducing a sessions table.
Each successful login creates a session record representing a single device or login instance. Along with the user reference, the session stores lightweight metadata such as device information, approximate location, login time, and last activity. This gives the system visibility into active logins without becoming overly complex.
At this point, the system becomes session-aware, but JWTs are still kept in place.
JWTs with a Session Reference
Instead of replacing JWTs, a small change was made: the generated JWT now includes a sessionId in its payload. The token is still signed, still validated as usual, but now it also carries a reference to server-side state.
On each authenticated request, the server validates the JWT and then checks whether the referenced session still exists. If it does, the request is allowed. If it doesn’t, the request is rejected.
This single check enables remote logout.
When a user logs out from a specific device, the corresponding session record is deleted. Any future request from that device will fail authentication, even if the JWT itself hasn’t expired yet.
This also made it possible to list the devices a user is logged in from. Since the sessions table stores the userId, a single user logging in from multiple devices simply results in multiple session entries. Logging out from a specific device is then just a matter of deleting the corresponding session record. The next time that device makes a request, authentication fails.
Top comments (0)