1

I am evaluating JWT as authentication mechanism for an API. The idea is to use JWT as API key.

One thing I want to implement is revoking API keys. Since revoking involves a state change in my backend, I am losing the main advantage of JWT, which is its statelessness. I was thinking to do a DB check on every auth call to see if the token was revoked.

Now, if I am already checking the DB, why am I storing state in the JWT at all? Could I not just ignore the payload and header and only use the JWT signature as API key? My DB could include revocation status, JWT header and JWT payload.

I could not find anything about this approach online and wanted to ask if I am missing some security considerations or if I am overcomplicating things.

1
  • 1
    Why do you want to use JWT? Why don't you use just a random string as a token?
    – mentallurg
    Dec 19, 2022 at 22:56

1 Answer 1

2

The short version: Technically yes, I suppose, but it's a terrible idea! You'd have to store each user/client's entire JWT in the server (not just the headers and payloads) or else authenticating a request would be O(n) on the number of active tokens. You lose every advantage of JWTs, while keeping some of their disadvantages. Just use an opaque, random string (from a cryptographically secure [pseudo-]random number generator) instead... like people have been doing for decades, since approximately the dawn of webapps, and often still do today.


Long version:

The standard solution to this problem - dating back to well before JWTs, or indeed JSON, were invented - is simple: a cryptographically random string of sufficient entropy (typically 128 bits) is generated on the server, encoded in a text-friendly encoding such as hexadecimal (base16) or base64, and both stored in the database (or other session store) and sent to the client (usually, but not always, as a cookie). This "opaque session token" or "API key" is used as the lookup for an authenticated session (session token), or as a long-term persistent credential (API key). Ideally, you should hash the token before storing it on the server (and re-hash whatever the client sends you before lookup), especially for long-term tokens, as this avoids two threats: timing attacks and read-only DB leaks exposing valid tokens. It also makes it less likely you'll screw up in a way that introduces SQL injection risk. However, other than that, it's really about as simple as it sounds.

Some notable advantages of this scheme over JWTs:

  • Revocation is trivial; just delete from the server. This can be done at any time, with or without asking the client to also delete the token, and the effect is immediate.
  • The token exposes no information - not who it's for, or even whether or not it's valid - to a reader (thus the term "opaque token"). You can achieve this with JWTs by adding encryption in addition to signing, but that adds complexity to an already-complex system, and usually the headers are in plain text anyhow. In theory the data in a JWT isn't sensitive (as such, I generally recommend against encryption because it's more likely to introduce a vulnerability than block an attack), but ideally you shouldn't expose any info you don't have to.
  • No (meaningful) single point of failure. With a JWT, you absolutely must keep the signing key secret; an attacker who obtains that key can trivially authenticate as any arbitrary user for any amount of time, until the compromised key is revoked on the server (including at all verifiers). With opaque keys, the equivalent compromise requires write access to the session store; if you have that, you almost certainly don't need it because you've already completely compromised all data stored on the server, and if you can do that it doesn't matter who you've authenticated as at all. In other words, opaque tokens are impossible to forge (assuming sufficient entropy is used).
  • Simple, mostly-foolproof design. While JWT-handling libraries have gotten a LOT better over the last decade, there are still sites out there that make all kinds of terrible errors trying to deal with JWTs as a format. For example, failing to reject alg: none (unsigned); failing to verify that the alg is correct for the key (a valid and secure ECDSA/Ed25519 key is extremely insecure if interpreted as an RSA key); failing to canonicalize the signed part of the token correctly, failing to verify the timestamps correctly... any of which can lead to relatively easy authentication compromise.
  • No need for refresh tokens; token lifetime can be whatever you want. Unlike JWTs, which - due to being irrevocable - must always have very short lifetimes and thus need a whole other type of token (a refresh token, which is almost always just an opaque token anyhow!), opaque session tokens are inherently revocable and thus can last as long - or as short - as you want all by themselves. They do require more DB hits than a session token does, but you only need the code and infrastructure for handling one type of auth token, not two.
  • Dynamic update without reissue. With a JWT, if I want to adjust a user's access level (e.g. to remove a permission), I have to generate, sign, and send a new JWT to the client, and hope the client switches to using that one instead of the old one. With an opaque token, I can make a change to the server-side roles/permissions/whatever field at any time, without the client being involved at all or having any ability to prevent this. (Note: even with opaque tokens, it is essential to replace the token any time a new session is created or a session gains new privileges, lest you face a "session fixation" attack.)
  • Short tokens. 128 bits, base64-encoded, is a mere 22 characters long (24 with padding). Even an unnecessarily long and secure 256 bits, using the simpler hex encoding, is only 64 characters long. Meanwhile, a JWT is almost always much longer than that - the signature alone is generally 256 bits if ECDSA/Ed25519, or 2048 bits if RSA, plus there's the other sections and a few characters of structural overhead - which bloats your headers, consuming bandwidth and hardware resources (it is a sad fact of the world that some JWTs are too large for some web application server hardware).

This is a strange question, because you seem to be starting from an assumption that JWTs are the only acceptable form of authentication token. That is completely, utterly, false. Maybe I'm wrong about your (mis)understanding, but... You may have heard the saying, "those who do not study history are doomed to repeat it"? That applies to programming as much as everything else.

JWTs are a relatively new form of auth token (dating only back to 2010), and one that comes with substantial tradeoffs in complexity and security for the sake of two goals: scalability (through server-side statelessness and avoiding DB lookups / session caching) and verification without being the issuer (through public-key signatures). If you aren't in need of either of those features, then JWTs are worse in almost every way than the classic random opaque session token.

From your question, you don't need any of the benefits of JWTs; you don't need statelessness or seem otherwise concerned with maximizing scalability, and you aren't saying anything about separate issuance and verification. You don't even want to use a JWT as a JWT (that is, you aren't looking to include any data in the token anyhow). Thus my confusion: have you really never encountered an API key, or session token, that wasn't a JWT?

1
  • I have seen API keys that were not JWT, but could not figure out what they were. Your answer makes a lot of sense and clears up my confusion. Thank you!
    – Nopx
    Dec 20, 2022 at 9:25

You must log in to answer this question.

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