1

Let's say I have a MySQL database with thousands of user accounts in it. These accounts contain lots of data, but for verification purposes, they each contain a username and a (hashed and salted) password. Now, when a user requests signing in, I will take a username and password from them, transfer it via WSS to a Node.js server then transfer it via HTTPS to a PHP file on another server. On that server I will look up the username in the MySQL database, and if I find an account, I will hash the password and see if it matches that username's password. If they both match, then I want the PHP file to create a "verification token" of sorts, save it (and associate it with the account verified) and send the token back to the Node.js server. I then want the Node.js server to send that token back to the client and for the client to save that token. Now the next time the user connects to the Node.js server via WSS, I want the client to check for an existing token, and if it exists I want it to send that token via WSS to the Node.js server, the Node.js server to send that via HTTPS to a PHP file, and that PHP file to see what account that token belongs to, and complete the sign in...

So I guess my first question would be: Is this a good model? Would this be a good idea, and would this be secure, or should I do this differently?

My second question is: What would be the best way to go about generating this token? Should it be completely random? Should it be a combination of letters+numbers? Should it be a hash of some random values? How should I go about the generation of this "token"?


To clarify, I'm not asking how to build a server or make requests or transfer data or anything of that sort, I'm merely asking what is the most secure way to generate a "token" that can be used as authentication to the same degree that a username+password can be used.

Thanks in advance! I'm sorry if this is a really stupid question.

1
  • Relevant to the token: crypto.stackexchange.com/a/80441 F.Grieu 2020, which is generated from a cryptographically secure DRBG: stackoverflow.com/a/3532136 T.Pornin 2010 - if you're on modern Linux then a solid choice would be to use /dev/urandom, unless you have some specific reason not to. You will also need to consider the method by which the password hashes were generated in MySQL, as this may no longer be appropriate for the higher-exposure web API. Please edit your question to clarify how the current password hash is generated.
    – brynk
    Feb 23, 2021 at 1:53

1 Answer 1

1

Short version:

Tokens should be either completely random, or cryptographically signed data (either asymmetric signature algorithm such as RSA or ECDSA, or message authentication code such as HMAC) using a key only the server knows. In the latter case, everything except the signature can be whatever you want (including simple human-readable text), but there are standard formats with existing libraries and I recommend you use them.

If you want to go the "random data" route, it's sometimes a good idea to store a hash of the token server-side, and hash the user-submitted token before validation (this is an easy way to avoid timing attacks on the check).


It sounds like you're trying to re-invent the "access token + refresh token" model. Leaving aside the probably-irrelevant stuff (why do you have a Node server sitting between your users and a PHP back-end?), it sounds like you're trying to develop some sort of long-term access token.

In theory, this is trivial; just generate a securely random string of bytes - 16 bytes out of /dev/urandom is fine - and then hex- or base64-encode that and use it as the access token (provided to the client, and the client transmits it with every request). The server maintains a dictionary in memory of user sessions and, upon receiving the token, looks it up in the session dictionary to retrieve the user info. The sessions have an expiration (possibly known to the client, but enforced by the server after which the user needs to actually log in again, but the expiration can be years or decades away if you really want it to. The server should also support invalidating a token (logging out) on demand, before it expires.

In practice, the trivial approach doesn't scale. First, anything that requires storing state on the server gets expensive when you have lots of users, especially if the sessions are long-lived. Second, this breaks down when you switch from a single back-end server to a cluster, because now you either need to cache sessions across the whole cluster and keep them in sync, or you need to route each request to the correct back-end server in the cluster (the one that has the relevant session). Both approaches are possible but both kind of suck.

The common solution, then, is to put as much state as possible on the client rather than the server, but sign it with a key known only to the server so a malicious user can't modify their session state (for example, to impersonate another user). This is typically done using a JSON Web Token, abbreviate JWT; these are a standardized three-part format for tokens that encompasses metadata (issuer, expiration, etc.), session data (user identity, permissions, anything else you want), and a signature over the other two parts (to prevent modification). The great thing about JWTs is that they require no server-side session state or DB lookups at all - if the token is within its validity period and the signature checks out, you can trust all the data in the JWT - so they are great for scaling. The problem with JWTs is that there's no good way to revoke them or dynamically lower their privileges; because the server stores no session state, it can't tell when you want to modify a session or end it prematurely.

The solution to that last problem is to make the JWTs very short-lived - typically a few minutes - and add "refresh tokens" (which is basically the random string mentioned above, stored in the DB on the server side). The refresh token is only used when a JWT is expired, or possibly just when it will expire soon, and serves as a long-term token that can be used to retrieve an updated JWT. While the JWT can be validated without needing a DB lookup, refresh tokens can't; they are stored in the DB along with any other relevant information. However, a user might make hundreds of requests that use the JWT in the time before needing to refresh it, so the load on the DB is still very low compared to a session lookup on every request, and you still avoid the state synchronization issue.

The total process looks like this:

  1. User visits login page, provides credentials.
  2. Server validates credentials, generates a refresh token and an access token (probably a JWT) back to the client.
  3. Client includes the JWT with every request for a few minutes, allowing the server to keep track of session state without needing to store that state between requests.
  4. Client periodically uses the refresh token to request a new JWT, which will be valid for a few minutes more.
  5. If the client goes away without logging out, the JWT will expire but the refresh token will remain valid, so the user can resume their session without needing to log in again just by using the refresh token (as in #4).
  6. If the client logs out (or the session is otherwise terminated for some reason), the refresh token is deleted from the DB. The user could in theory continue using the access token (JWT) for a brief time, but can't refresh it any more and would need to log in again.

With all that said... you should seriously consider using some existing authentication and session management library, rather than trying to build your own. Not all of them are good, but some of them are... and most (though not all) are written by people who didn't need to ask this question. For example, "salted and hashed" is not sufficient for password storage. You need a slow hash function, ideally one explicitly designed for password hashing. Using a well-reviewed open-source library will save you from needing to know all this stuff, and avoid common pitfalls, while also saving you development time.

At the very least, if you do use JWTs, use a library for them. Generating and checking random tokens isn't too hard to do securely (though ideally you do want to avoid things like timing attacks in the string comparison), but more complex token types with digital signatures and so on should be handled by library code. Honestly, though, if you have to ask "completely random" vs. "a combination of letters+numbers", you should let other people handle this.

5
  • Also, since I'm using WebSockets and not HTTPS requests to communicate with the client, I don't actually need to pass the token with every request. The only thing I was asking was how to create a token. Not how to manage registration/verification systems. I spent an hour or so reading about JWTs this morning and had already decided that they wouldn't do what I needed. I simply need a secure way to generate a "random" token that can be used as a substitute for username+password, and your answer doesn't help me towards my goal. Feb 23, 2021 at 9:45
  • Finally, to answer your question of "why do you have a Node server sitting between your users and a PHP back-end?" That's because I have developed an online game that works with WebSockets, and runs on a Node.js server. I decided that I'd rather have a MySQL database on a separate server of mine and manage it with PHP rather than being forced to use one of Heroku's native "add-ons" to manage a database there. So the clear solution was to simply link the Node.js server with another server, and I decided on PHP as my language for that other server since it doesn't support Node.js. Feb 23, 2021 at 9:47
  • @OOPSStudio I'm not seeing insults in this answer. I think you might be taking some of the comments too personally. CBHacking is trying to impress upon you the importance of getting this right and using established options. This is not something you should make on your own.
    – schroeder
    Feb 23, 2021 at 11:53
  • I see no reason why I shouldn't take the comments personally when they're written in response to things I've said... I read the Code of Conduct and his post does appear to be in violation of what is written there. Feb 23, 2021 at 20:35
  • Also, yes, I know the importance of established options, however since I'm using WebSockets, 90% of what I'm doing is already using established options. The only thing that I need to know is how to make a secure token which can be used as a substitute for username+password. I don't need to verify that the user is signed in for every request, only one time at the start of the WebSocket connection. (So JWTs are not the answer I'm looking for.) Feb 23, 2021 at 20:35

You must log in to answer this question.

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