0

For my website that is currently in development, I am trying to figure out a way to handle authentication. I am using mysql as my database and nodejs for my server. Currently, my website does the following: Whenever a user signs up, the username, password and email are sent to the server and the password is hashed with the argon2 algorithm. Then, I use a SQL query to add the username, email and hashed password to my database. Next, for authentication when a user signs in, I verify if their password matches to the one in my database for their username, and if it does match I give them a special session token. I would have used something more popular like JWT's, but I am using a relatively new framework for my website (Svelte Kit) and I did not find any good tutorials for implementing them. Instead, for session authentication I am using a JavaScript object and encrypting it using createCipheriv and the aes-256-ctr algorithm (after making the object a string with JSON.stringify). I am storing this encrypted object with localStorage in the browser, and checking if the credentials are valid every time localStorage is changed.

To summarize, I just want to know if this way of storing information is secure and reliable for when I make my website public. I am new to information security, so any suggestions to making my approach more simple or secure would be greatly appreciated.

2 Answers 2

2

Yes you should, this is dangerously insecure. Encryption does not prevent a message from being modified. CTR mode essentially turns a block cipher (such as AES) into a stream cipher. Stream ciphers are extremely vulnerable to bit-flipping attacks. If an attacker can guess what portion of the ciphertext identifies them (the user id, email, etc.), they can flip bits in the ciphertext to flip the corresponding bits in the plaintext, thus changing who the server thinks they are or what permissions they have.

To prevent this, you have three options:

  1. Add a message authentication code that protects the integrity of the ciphertext. This could be a detached authentication code, such as an HMAC (which would be appended or prepended to the token, just like the IV presumably is), or it could be an integrated authentication tag if you switch to a mode of operation that provides authenticated encryption (such as the widely-used GCM). Authenticated encryption would be my recommendation between these. You'll need to include the authentication tag with the ciphertext, and pass it to the decryption function to verify the message hasn't been tampered with.
  2. Switch to a signed, rather than encrypted, token. This is how JSON Web Tokens (JWTs) work; the actual content of a JWT is plain text (metadata + claims) and then there's a signature section that typically uses either an HMAC or an asymmetric digital signature algorithm. (You'd want to use HMAC; it's much faster and shorter for a given key length and you don't need to give anybody else the ability to verify your tokens). You could either use a JWT library (recommended), implement them yourself (NOT recommended; it's a simple format but people get it wrong surprisingly often), or "roll your own" simple version e.g. by taking your current JSON object and appending an HMAC (also not really recommended).
  3. Switch to an opaque random token, stored server side. Sometimes specifically called "session tokens" (although that can apply to any token used to identify a user post-login), this is one of the oldest, simplest, and most secure ways to handle sessions: after logging in, the server generates a random blob (typically 128-256 bits of cryptographically secure entropy), hashes it, and stores it in a database, cache, in-memory object, whatever. The unhashed opaque token is also sent to the client, of course, and needs to be returned on every authenticated request. The server hashes the client-presented token and compares it with the collection of currently-valid hashed tokens to identify the user. The downside of this approach is that it doesn't scale very well; if you want the tokens to be long-lived, or to have more than one server handling requests, then the tokens need to be stored in persistent storage (such as a DB), and that makes for an extra DB lookup on every authenticated request. Not ideal.

A few other concerns here:

  • How are you storing the key? One key problem with any auth system built around signing (or encryption-with-integrity-protection) a token is that, if an attacker gets ahold of the key, they can forge arbitrary tokens to gain access as any user they want. Thus, the key should both be stored as securely as possible (an HSM is ideal), and it should be possible to rotate it on demand.
  • How are you generating and transmitting the IV/nonce? It should be unique for every token and long enough to never risk repeating. You generally append or prepend it to the ciphertext.
  • How are you handling logout? One critical weakness of all "stateless" session tokens (that is, things like JWTs or your approach, where no info about the session is stored server-side) is that there's no way to revoke them. As far as the server is concerned, they are valid from their creation until their internal expiration is reached (you did include an expiration time in that JS object, right?). JWTs often solve this by making their expiration very short (single-digit minutes is common) and having a long-lived "refresh token", typically an opaque random token as described in approach #3 above that is used to periodically get a new short-lived stateless token. Thus, if the user logs out or their session gets revoked, even if an attacker has access to the stateless token, it will stop working very soon.
  • Is there a reason you're using local storage instead of a cookie? Cookies are simpler to implement, and have many flags that can add security (including using the "HttpOnly" flag to make it so that even JS running on the client - such as a malicious XSS payload - can't steal the token). Their main security downsides are an overly broad default domain scope (but you can scope them down to just a single subdomain) and being sent automatically on every request. The latter is what makes CSRF possible. However, CSRF can be mitigated in multiple ways. Needing to mitigate CSRF does likely eliminate the complexity advantage of cookies, but it does still let you avoid the risk of token theft.
  • What cost parameters are you using for Argon2? You want to use as much CPU and RAM as you can without risking denial-of-service on the server. This limit will depend on your hardware, and will increase over time as you upgrade that hardware.
  • What password quality checks do you have? This is a large topic that's somewhat out of scope for this answer, but suffice to say: length is important, "complexity" is somewhere between minimally useful and actively harmful, and what you really want are to ensure the passwords haven't been used anywhere else.
  • How do you handle lost passwords? That's a common attack vector for authn systems.
  • Do you have any support for multi-factor authentication? MFA should be available, even if not enforced, for all sensitive sites/services. TOTP ("Google Authenticator") is probably the best combination of ease and security, though I'm personally a fan of Webauthn (which can be very frictionless and also provides phishing protection). Note that SMS is pretty weak and should only be used as a second factor if the alternative is no MFA at all.
  • How do you handle DB interaction? Do not, ever, form SQL queries through concatenation with user-controllable content; that's a recipe for SQL injection. Use parameterized queries (prepared statements / stored procedures) instead.
  • Do you provide redirection, and if so, how? That's a common area of security vulnerabilities (ranging from open redirect to XSS) in authentication pages.
7
  • I assume they would use the e-mail address for password resets. (They have one of those annoying sites that make you enter your e-mail address and choose a username. I always put my e-mail address as the username, if possible. Some sites use both the e-mail address and the username for password resets – is this more secure or just unnecessarily complex?) OP: You do verify the e-mail address, right? Feb 3, 2022 at 13:38
  • Your answer states that my method of session auth is insecure. Is the way I am storing the data in my database secure? (I did use a prepared statement with MySQL to prevent SQL injections) Feb 3, 2022 at 14:06
  • I will switch to JWT's to auth, and I am wondering if using a httpOnly cookie or using the Authorization header is more secure Feb 3, 2022 at 14:56
  • @BrianDrake As I said in my question, this site is still in development. I have not implemented password resets but when I do I will verify the email Feb 3, 2022 at 15:10
  • You don't specify how you're storing the data in the DB, so I can't conclusively answer that. Many argon2 libraries return a verifier string that contains all of: the algorithm ID, cost parameters, salt, and hash. Such strings are safe to store in the DB and can be passed (along with the unhashed password) to a "verify" function that will check a password.
    – CBHacking
    Feb 4, 2022 at 4:12
0

The state of the art for authentication can be found here: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

I'm not a strong expert in Web but here are my points :

  • Argon2 is a good choice
  • Consider securing MySQL against SQL injection (like with prepared statement)
  • Best practices recommend to make the bad authentication of an existing user as long as a bad authentication for a non existing user (to avoid user enumeration)
  • Maybe you can consider XSRF protection by asking for password for critical function (account deletion, ...)

I'm not familiar with your session authentication scheme, I cannot say if this is the correct way.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .