One approach is
- Application requests a license key at startup, attach either a random or hardware based id and the username to the request. The server finds the appropriate key and marks it as used, with the id and other relevant data. This can be done by using a separate 'used licenses' table or by updating the licese key row.
- Application sends a keep alive message to the server at some interval (a minute?). If there is a missmatch between the id in the keep alive message and the used license the application is told to terminate.
- If the server cannot be reached for some amount of time ( a few hours?), the application might terminate, display a warning, or just continue running.
- If the server does not get a keep alive message for some time it should release the key. This is to prevent an application crash from keeping the key forever.
This ensures only one concurrent application instance per key. The behavior if a user tries to launch two instances is up to you. You might want to re-assign the key to the later instance, causing the original instance to terminate, or reject the request, preventing the new instance from starting, or let the user decide. Benefit of using a hardware based ID is to allow multiple instances to run on the same computer (if you want to allow it). It would also allow restarting the application after a crash without waiting for the key to timeout.
If this scheme is meant to support multiple independent installations at different companies a two stage solution could be used. This reduces the dependence on the remote server, but reduces your control somewhat.
- Local server fetches all serial keys from the remote server every day and replaces the old keys. Keys are valid for a short while (a week?)
- Local server implements behavior above.
You could also allow the users to lock a license key to a specific machine. That machine could then run the software without internet connection, but the user cannot run the software from any other computer.
Note that this does not provide strong guarantees. A determined attacker will almost always be able to break systems like this as long as all important logic is run on the attackers computer. The goal is usually to increase cost of abuse to more than what a license costs. You might also want to monitor the system to detect patterns that might indicate systematic abuse.