I'm building an app where the client will be issued a JWT. The JWT will be passed to my API for every request. I'm leaning in the direction of hosting my webserver and SQL server separately from each other using EC2 and RDS.

SECRET = bin2hex(random_bytes(32))

My question is... which is the better practice for storing the secret?

  1. Creating a file on my server (outside of my webroot) that stores a single secret that is accessed by my API's PHP scripts.
  2. Creating a file on my server (outside of my webroot) that stores a list of individual secrets per user that is accessed by my API's PHP scripts.
  3. Creating a SECRET column in my database where each user has their own JWT secret.

My thinking on #2 and #3 is preventing a brute-force attack from potentially affecting everybody.


Using per-user secrets - stored in either the database or the file system, or indeed anywhere else - completely misses the point of JWTs. Well, one of the points, and using symmetric secrets (rather than public keys) means you don't get the other, either.

If you have to look up a value before you can even verify a JWT, you've destroyed the scalability and statelessness of the format. Sure, in practice you'll store lots of info in the DB, much of it per-user, that you'll be requesting all the time anyway... but if you're going to make your session management system depending on those kinds of lookups then you should really consider just using random blobs for your session tokens. Same lookup hit, less compute time after retrieving the secret to verify it, and you can actually revoke them without complicated shenanigans.

To answer your actual question, the ideal setup is an HSM of some sort (such that actually extracting the keys is just not possible), but lacking that the usual approaches are:

A. Stored in a KMS (Key Management Service) that the server can talk to; server retrieves the secret(s) at startup / periodically and stores it/them in RAM. Of course, proving identity to the KMS often adds another layer of "where do you store secrets?"; the main advantages of this scheme are a central location to manage keys for a whole cluster, and the ability to keep the keys off of any persistent storage outside one particular system.

B. Stored in an environment variable or command-line arg passed to the server. Obviously the process that invokes the server needs to know the secret too, but this can be combined with the previous approach to have no persistent storage on the server host but also allow creating arbitrarily many short-lived instances of the server without each one pinging the KMS. Alternatively, supports starting the server process via remote command, with the secret not resident anywhere on the server host (even in memory) until the server starts.

C. Stored in a file outside the webroot, but somewhere the server has access to (your #1). Ideally, the location should not be somewhere the server has persistent access to, though, either because it drops privileges after reading in the secret, or because the secret is read through a named pipe or something and not available on demand.

For simple systems like you're probably envisioning, where quick development and low cost matters more than security in the extreme edge cases, you probably want to go with approach #1 / C, if you use JWTs at all.

Speaking of extreme edge case concerns, brute-forcing any reasonable length (typically cited as 128 bits i.e. 16 bytes) of cryptographically random data (which PHP's random_bytes provides) is not remotely a concern. With a 256-bit (32-byte) value, assuming Moore's Law continues on track (which it hasn't quite, lately), the entire world's compute capability, working together, might be able to brute-force a single secret... if you give them three to four centuries to do it. You probably won't care by then.


Jwt tokens are typically produced by the server and issued, you don't need to store it. They can be encrypted but that is not the very important part as you don't need to store any sensitive information in it. Tokens are also self contained, meaning they have enough information on their own and don't need to be stored anywhere.

Now, about the key part, if your key is long enough (atleast 128 bits) then you don't have to worry about it being brute forced. If have to safely store it to prevent it from being stolen. 2048 bit RSA keys (and since you have a public key you can easily launch offline attacks) have weaker security than 128 bit symmetric key yet they are still used in many websites(Not recommended tho modern day bitcoin mining machines are just too powerful.)

The way Jwt tokens are made unforgeable is via use of an appendage, a symmetric key MAC or a digital signature. With your scheme you seem to be opting for MAC which is OK as long as it is verified by only the server which issues it, and the secret stays with it. If other servers may need to verify your token they you would need a digital signature signed by your server's private key which others can verify using your public key.

Now on to security considerations: Cryptographically, whether you use MAC or digital signature they should be resistant to existential forgery under chosen message attack. Ask at Crypto SE, if you want more information about it as it would be off topic here. While for most practical purposes, resistance to selective forgery might suffice but in cryptography, we keep security margins higher than what is apparently required. You should be careful about the header too. Be sure to check and not accept if the algorithm on the header does not match yours. Making signature as NONE or converting digital signatures into MAC of public key are well known attacks on Jwt Tokens and if you are not careful, then your Jwt library might accept appendage-less token or one with MAC with public key.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.