The private keys NEED to be stored in the users' machines, not anywhere else. You could technically use local storage or similar for this, but the correct way to do it is to store the private keys in the browser's built-in cryptographic key storage. See https://developer.mozilla.org/en-US/docs/web/api/subtlecrypto, and the methods for generating or importing a private key, and then for using that key to sign data.
If your users need to be able to use multiple computers or to have backups of their keys in case that their computer dies, there are a few options.
- Let each user have multiple key pairs, one per machine (including a backup). This is less convenient from a key-management perspective - you'd also need multiple public keys for each user, and to let all counterparties know whenever any user adds a new public key (or else have a trusted way to look up what each user's current and past set of public keys is) - but it has advantages too. Each private key can be generated in situ on each machine, and never leave it in any form. It can be marked non-exportable to make it harder for an attacker to exfiltrate the key. A single key can be revoked (if you fear that e.g. the computer it's on might be compromised) without that user losing all of their keys.
- Let users transfer keys between machines themselves, without them going into centralized storage that could, in theory, access all of them. This is somewhat inconvenient for the user, greatly increases the risk of that specific key being compromised, and means that when a key is (or might be) compromised and gets revoked then the user has no trusted keys anymore. But, it hugely simplifies public key management; each user has one, known, public key, and the mapping between public keys and users doesn't change except when adding or removing a user, or needing to rotate/revoke a private key for some reason.
- As a compromise between these scenarios, allow the private key to be stored in your infrastructure, but before it ever leaves a user's computer, encrypt it with a symmetric key derived from a password that the user enters. This password should NOT be the same one that is used to log into the system (or, if it is, it needs to be hashed client-side in two different ways, one to derive the private-key-encrypting symmetric key, and one to pass to the server for authentication). Thus, the user has only one private key, but it never leaves their machine in plain text. The user can import their private key on new devices using the private key's encryption password (and presumably being logged into your infrastructure), but the actual private keys can't be decrypted and stolen from your server even in the case of total compromise[1]. The main downsides of this option are that your user still loses their only key pair if it has to be revoked for any reason and that your user must remember a password (possibly an additional password) though that's generally only actually needed when enrolling new devices.
Any of these options will be more secure, and closer to best practices around handling private keys, than what you're doing now. You can potentially have the client do nothing more than store the private key and relay data-to-sign in and signatures out, or have the client implement the entire document signing system in JS or WebASM or so on such that your servers merely need to store and retrieve the documents rather than implementing any actual signature-based logic themselves, or something in between.
The main issue you would run into - which you already have - is key authentication. How does somebody receiving a document know that it was signed by the right user? Unless the recipient knows for sure which public key corresponds to which user, they can't be sure that the document was actually signed by somebody else (possibly not a user at all, such as malware running on the server). The recipient can ask the server "hey, what's user X's public key?" of course, but a malicious or compromised server might lie in its response, perhaps returning the attacker's public key (which was used to fraudulently sign the modified document) instead of the legitimate user's public key (which can't verify the signature on the modified document). There are many attempts to solve this problem - public key infrastructure (PKI) with certificates and certificate authorities (as seen with TLS and S/MIME), web of trust with public key signing (as seen with PGP/GnuPG), trust-on-first-use (as seen with SSH), server-based key exchange with out-out-band verification (as seen with Signal, WhatsApp, and similar networks), and perhaps some others. All have drawbacks.
[1] Caveat on this point: with browser-based encryption, there's always the risk that a malicious or compromised site serves the browser a script that captures the key in plain text right off the client, or captures the user's login keystrokes, or whatever. This issue makes it impossible to build a proper zero-trust secure system on the web.