Handle concurrent login in web app
In many web applications, there are requirements to limit concurrent login of a single account.
We assume these scenario are considered as “concurrent login”:
- Login from 2 different devices (e.g. 1 PC & 1 mobile phone)
- Login from 2 different browsers
- Login from browser private mode and normal mode
Multi-tab is not considered as “concurrent login”
There are 2 approach to tackle this problem, we can either
- Kick the 1st login when new login detected, or
- Block the 2nd login attempt when someone is already logged in
1. Kick 1st login
New login kicked previous login, there are several benefits of this approach
- No need to consider logout action, nowadays many users don’t bother logging out from web app when they close browser. This approach works in this case
- No need to detect session close event, detecting tab / browser close event on client-side is difficult if not impossible. Behavior of onbeforeunload(), onunload(), Navigator.sendBeacon() is different even in major browsers
- Best cross browser/device compatibility, the only client-side technology involved is Session Cookie (or a means to store the LoginID on client)
- Easy to implement, no polling on client-side / daemon task on server, just simple request / response call.
Basically there are 2 parts:
- On Login a LoginID is generated and stored in a shared Dictionary (e.g. DB) as UserID / LoginID key-value pair, then the LoginID is encrypted and store in Session Cookie. Encryption prevent LoginID from maliciously modified on client-side
- On each request the LoginID is retrieved and decrypted from Cookie, then compared against Dictionary value (for that UserID). If the values match then proceed the request, if not it means the request is from previous login session that is already invalidated.
One security concern is Session Hijacking, SSL + secure flag on Cookie could prevent hacker from accessing the Cookie by network sniffing.
2. Block 2nd login
Block new login attempts after first login.
The key is to determine when the first login session ended:
- User logout action
- User close tab / browser
- Crash of tab / browser
Point 1 is easy but 2 & 3 are tricky, we can detect tab close by onbeforeunload(), onunload() but browser close is hard to detect. Also, sending request to server in onbeforeunload(), onunload() can only be done by Navigator.sendBeacon() that not all browsers support.
Another approach is to send Heartbeat messages to server, and logout the user once Heartbeat expired. This approach should work in most circumstances, but note should be taken in multi-tab scenario to avoid sending too many Heartbeat requests to server.