15 min read

JWT Patterns that provide real security benefits

Throughout this post we will keep an YUK WORD tally of things that are not security characteristics of JWT, that many so-called JWT experts (and everyone else) assume are "a thing" that really are just hand-waving nonsense.

Starting with; verify is not validation

YUK WORD verify

Verify vs Validation

A JWT library or framework CAN verify the token is well-formed. When you call any method by any name (yes even a method called validate!) in the JWT library that IS NOT your code, you are simply verifying the JWT is properly signed and the content of the JWT is not modified.

YUK WORD integrity

An integrity check, that the signature was reproduced, is not 'validation' that the content is anything but data (an JWT claim), i.e. you got some data from a external provider (OIDC producer generated a JWT) you are going to verify the data was the same you are reading as it was when the JWT was generated and signed. i.e. that's verifying the signature.

YUK WORD claim

The producer 'claims' certain data points, and you have done nothing but determine the data was the same data they sent. That is the validation security characteristic, and you must build your own validation solution, a generic validation is inherently impossible to give to you via a framework as what validation means to you is entirely dependant on the claims made, the trust model, you have with the provider, and the claim data purpose to you specifically.

YUK WORD signature

There are two JWT modes, both modes result in a signature (and therefore potentially have an integrity assurance), but only one mode will offer data protection/privacy/confidentiality.
A signature can be generated using HMAC as the first common mode, and this is the mode that does not protect anything, claim data is public.
The second mode is PKI based and more popular with so-called "big-tech" and is the mode that has potential to derive a signature using a public key, and optionally do some data protection too if the private key is properly managed (almost never is).

YUK WORD assurance

We gain assurance of an assumption of some security characteristic by first being entirely deterministic of the security mechanism and precise about the guarantee's it provides. Any deviation undermines the security characteristic and we gain no assurance.

Both modes verify the JWT almost identically, but both require you manage a sensitive secret properly - and each mode could not be more opposite on this piece so I'll detail each:

Quick aside: there is also a none mode that literally provides zero security characteristics at all, it's just JWT for funzies and hobbyist. There is never a reason to actually use none not even for a joke, you literally wasted your time after writing 1 character into your code and you should just not use JWT at all, anything else would probably be better use of your time at that point.

Assurance for encrypted data (RSA/ECDSA)

If you are using HMAC variation of a JWT, then nothing is encrypted (skip ahead).

Whether or not you think RSA or ECDSA respectively are good (you may not even be aware of all the options), the only security characteristics for 'Enc-based JWT' we are concerned with for the purposes of verification are related to private keys:

  1. Generation of an asymmetric 'digital signature', a type of signature that is created with a private key, and verified with the corresponding public key. as opposed to;
  2. This is NOT the symmetric MAC (aka keyed-hash, aka HMAC) that requires both message creator and receiver to have all input information (a shared-secret) required to generate the MAC signature - RSA/ECDSA theoretically gains; non-repudiation (identity + audit) assurances
  3. Signatures of all variations gain the characteristic of integrity as it's primary purpose
  4. When a private key is shared we automatically lose authentication and non-repudiation assurances because if there are by-design 2 holders of the key there is no longer an identity inherent in holder-of-key authentication and without identity we cannot have an audit record that provides non-repudiation
  5. We never gain from signatures; privacy, confidentiality, authorisation, replay attack protections, and a long list of other assumed benefits associated to JWT, these must be giving us assurance by other mechanisms because signatures can't assure us of anything needed for these security characteristics

A replay attack is a critically important security characteristic because all previous messages that are verifiable authentic but a stolen and replayed are still considered authentic, breaking the assumed non-repudiation, authentication, and therefore we cannot trust the message after the first instance (and possibly not even on the first instance due to a race condition where the attacker can be first and any replayed message invalidate all previously trusted messages too).

YUK WORD trust

Once a message is verified it can only be trusted once trust-worthy authentication, and non-repudiation is establishing said trust through a process of verification.

For JWT purposes, there are several constructs around the handling of public keys; JWE, JWS, and JOSE that are all about serialisation but many explanations will refer to a public key but these serialisation methods have no inherent PKI purpose other than some JWT sugar to help developers without PKI training. We will explore JWE, JWS, and JOSE further below.

Security rant long aside: the unfortunate truth is even the authors of JWT/JWE/JWS are lacking basic PKI education by including phrases like:

The message is signed using the private key corresponding to the public key advertised in the header

That is non-sensical, what does 'signed using the private key' even mean? Public keys are for enciphering plain-text into ciphertext so that it may be deciphered by the private key, where as a signature (ergo 'signed') is one-way, it can not be deciphered by a private key ever, period, full-stop. And if we need to verify the signature at the other end, HMAC requires the same private key as input, HMAC is designed to be deterministic so that the identical input produces the identical output for that input, ergo the private key is now exposed at both ends making that private key useless at performing the PKI task of decipherment!

YUK WORD private

What does it mean! 'private'?

  1. Any data that intended to be read by a client (and encrypted by a JWT producer) should only be deciphered by the holder of the private key
  2. Only the entity that uses the private key to decipher the data should ever gain access to the private key, sharing a private key is, well, not keeping the key private
  3. The JWT producer should never have the private key, to encipher (encrypt) data only the public key is needed. This includes the generation of key's, and most importantly a private key used for decipherment must never be utilised for any purpose by any other system component as this would defeat the purpose of the key remaining 'private' to just the decipherment usage.

None of this is JWT specific, it is all a PKI fundamentals explained for a JWT implementation.

All assurance is lost when any deviation of the above 3 points occurs, no exceptions. If you think you have a real exception, then a private/public key pair system is NOT what you need for your use case, it is really that simple.

This brings us to the entire point of this section; encryption of data. An entirely distinct concern from Signatures and there should be nothing private/secret implemented that is shared for each concern. If any private key is used for the Signature it must not also be used for deciphering encrypted data.

We only gain the desired security characteristic of encrypting (encipher) and therefore decipherment when the only place a private key is generated, stored, used for decipherment, is fully controlled and private keys only accessible to the precise location where you are doing the decrypting.

An Enc-based JWT verification will assure integrity only. Additionally outside verification we gain data protection for the claims data but only when we have private key protections established first and this bonus characteristic is not related to JWT verification and if your implementation IS related you have a broken implementation due to the mis-use of a single private key with incompatible uses.

Assurance for signed data (HMAC)

The HMAC SHA-256 `"alg": "HS256"` is most common (but like above you're likely unaware of all HMAC options), the important thing is we acknowledge that HMAC offers no data protection at all and the JWT itself (the JSON token) is by0design not a secret and expected to be public.

Whether or not you think the algorithms are good, the only security characteristics we are concerned with for the purposes of verification are:

  1. This is a symmetric MAC (aka keyed-hash, aka HMAC) that requires both message creator and receiver to have all input information (a shared-secret) required to generate the MAC signature
  2. The receiver can verify the sender is authentic if the shared-secret being used is both; shared out-of-band (not over the same communication channel), and the shared-secret is trusted to represent the sender (only this sender) which is inherent trust that we cannot validate using HMAC or JWT options

i.e. the "A" in HMAC is a mechanism for unverified trust the sender is who they claim to be, and therefore the HMAC security characteristic we are concerned with is only that the message integrity can be assured. We will have to gain assurance for Authentication with some other mechanism that has linked identity that can be validated.

A HMAC-based JWT verification will assure integrity only.

Revocation is considered for JWT

If we are using x.509 standard for JWT, and there are revocation standards for OCSP and CRL then we do actually have revocation considered for JWT, it's that simple.

People who are the so-called experts at JWT working for the 'big name' company are just perceived as experts due to a reputation of 'big name' company. Real JWT experts could not claim JWT has no revocation built-in because to be an expert you probably read at least the jwt.io introduction page that clearly references X.509 revocation, and the experts probably read these standards too if they're doing a half-decent job implementing JWT.

So to be absolutely clear on revocation, it is not 'keeping a blocklist in a database somewhere' as you would hear from 'experts' at big name companies..
Sure "some big name companies" are doing it and 'accept it' as the thing they should do. But that is not the same as JWT design OR how PKI implements revocation. It is just what they chose to do probably never know the standard options ever existed, because they are 'smart' engineers and engineered the solution they thought was how to do revocation, and that became 'popular' among their tiny elitism circles.

Standards exists so we have a starting point that works for specific problems, instead of building bespoke revocation solutions, use standards; i.e. the cryptography primitives JWT intended.

But first ask yourself, WHY revoke? If you are using JWT HMAC (ergo Signature-only mode, no encryption, no secrets, everything in the token is public) then what are you actually revoking? You can not be revoking the actual JWT (which is just a public signature right?), you must be wanting to revoke some business logic you intertwined with JWT, which is not a JWT problem, it's how you are using JWT or rather how you are trying to revoke something unrelated by forcing the concept of revoking a signature (that is not a thing, there's no such security characteristic).

I would recommend using OCSP with JWT, instead of downloading the latest CRL and parsing it to check whether a requested certificate on the list, an OCSP can be created on the server-side and 'stapled' to tokens. Validation of a stapled OCSP assertion requires a mere time-based freshness check and fail-closed if the assertion is not fresh or doesn't exist, no need for any lookup I/O at all.

YUK WORD fail-closed

When you expect there to be a security control for revocation, then not being able to do verification and then have a failure mode that is 'open'; we could not get a revocation status therefore we will skip revocation checking and use the token anyway = not a security control for revocation. It is only a security characteristic that gives assurance when we ensure that revocation checking proves the token is not revoked and anything else means the token is untrusted, including not knowing if it is even revoked at all we should still not (yet) trust it until we validate our trust that gives us assurance it is not revoked before we use the token.

Authorisation benefits of JWT

It was described plainly here. in summary; JWT offers absolutely no Authorisation mechanism at all.

The common confusion is the JWT is frequently chosen to securely transport ACL information from server to client, as claims data. That is not Authorisation, to say JWT provides Authorisation is to say that a car provides coffee because you happened to take a car to the café - absurd! It deserves a;

YUK WORD authorisation

It's an exaggeration and the most absurd part is that Authorisation decisions should never be made on a client, and data about Authorisation can not be provided to a Server because it is the role of the server doing the Authorisation check to actually check data it has to validate the access attempt to know if it was authorised to perform that action. If I try, really hard, to see how JWT is used to transport Authorisation information (again not enable an Authorisation mechanism) it might be in a server-to-server transport where one server is seeding Authorisation information of the other server where OAuth2.0 and OIDC are good examples of a provider of identity might use JWT to transport Authorisation information to the consumer (i.e. server-to-server in the sense where one server is an OIDC provider that has many distinct OIDC consumer entities). Even with OAuth and OIDC the actual Authorisation mechanism is not part of the protocol, you given them your "authorisation_url" so they can make a call to your system where you implement the Authorisation mechanism and just tell them Authorisation True/False when they ask you.

How any Authorisation implementation is done, if it is actually implemented at all (often it is called Authorisation and OWASP Top 10 shows it's the number 1 broken thing in your app) you still have to actually implement yourself on your server. How that involves JWT as a provider or mechanism for Authorisation in any way is beyond a broken concept if it includes a client at all, it's just another example of uneducated security nonsense that is mixing words like Authorisation with something else that has it's own name like ACL, every angle you try to see where the security characteristic for Authorisation has potential to even exist for JWT, it doesn't. There's another;

YUK WORD access control list

XSS: Cookies vs JWT

Apparently where to store the JWT is the real question, not a comparison of which one to use exclusively, i.e. it is detailed that some JWT users believe browser local storage is better than a cookie. Everything about this section is complete nonsense, even a recommendation of what storage to use is not a security decision at all.

Browser local storage does not add XSS protection to JWT, even saying this is pretty redundant as browser local storage is not a vector or mitigation for XSS, it is just storing data for a site that cannot be read by other sites, it doesn't fix alternative storage options that introduce XSS so Browser local storage is not a mitigation for another storage-based XSS vector - besides if there is an XSS vector at all, in any XSS form, Browser local storage is still completely vulnerable to that XSS vector as are all storage options for a site.

YUK WORD threat

See? XSS discussions that include threats against Browser local storage are just nonsense, there is nothing to discuss. Maybe you're missing the meaning of threat, that a threat vector defines the threat but the threat mitigation has it's own word 'control'.

YUK WORD control

Now go back and read that last paragraph and replace "Browser local storage" with "JWT" and all the phrases are exactly the same meaning, JWT and XSS vectors are nonsense, JWT as a mitigation for XSS is nonsense, the whole XSS discussion is meaningless waste of time except to point out JWT or not JWT has no impact to XSS attack mitigation.

That out of the way, let's just make what should be obvious so clear that we never forget it again: ALL JWT ARE DESIGNED TO BE PUBLIC TOKENS

There is nothing about the JWT itself, to be concise the 'token' that is transmitted and written to storage, there is nothing in that JWT that you need to protect. Store it wherever and however you see fit, based on your storage needs, i.e.

  • Store in a Browser local storage if you want it to persist across sessions
  • Store in Browser session storage if you want the token to be purged when the browser session ends
  • Store in a cookie for general purpose needs that may change and not be bound to a storage decision

Now, there are 'secrets' that you must protect and we covered what the secrets are but we have not discussed how to store them safely.

Secure storage options for JWT secrets

JWT offers no guidance on how to deal with your PKI secrets, or you sensitive shared-secrets for HMAC. It assumes you know how to protect your secrets depending on your own implementation needs.

Implementation of JWT comes down to what client technology will be used to deal with JWT Signatures and decipherment, which both require the secret storage option chosen for purposes of storage of PKI and secret keys and each platform has suitable options available:

  • Modern web browsers: Credential Management API
  • Android; KeyChain
  • iOS; Keychain
  • Linux desktop; dbus
  • Windows; Credential Manager
  • Macos; Keychain

There are also programming languages that offer an agnostic approach across all or most of the above, I have used Python keyring with success for this.

YUK WORD secrets

The point is; JWT is not responsible for telling you how to store your secrets, nothing will do that for you. You either learn the skill to manage your secrets or be a victim for ignoring what has become a basic skill for all technology users (not a programmer specific skill to learn).

Hashicorp Vault was discussed extensively in context to JWT, why I cannot be certain. it is not a solution for JWT secret storage that offers better security characteristics at all. In most cases it makes the security characteristic a higher risk because now you're not exposing a secret that may be used to forge a JWT signature or decrypt some JWT claims, you're exposing a vault access credential! Which ultimately should still utilise something from the list above, so we are not really talking about JWT at all when we explore Vault, we're off-topic completely. But let's explore the security characteristics anyway.
Assuming the JWT secret is generated by Vault, stored in Vault, and when needed is requested from Vault API on-demand and discarded after single-use never to be persisted/cached/reused for subsequent use in the browser - we have the following added complexity and security considerations by moving the secret to Vault:

  1. Management of the Vault credential; how do you ensure the Vault credential is retrieved in the first place, and ensure it is not exposed in a million threat vectors
  2. Ensure the secret can only be accessed by this workload, not even by a Vault admin or any user. Any access beyond the workload that does decryption breaks the PKI security characteristic gained from using RSA/ECDSA, and for HMAC the same applies, any access outside the signers means we expose the HMAC secret to potential forgery
  3. Vault credential API should have the transport of the secret properly secured with TLS1.3 (or limited to only strong ciphers for earlier versions). TLS aside; we are now also introducing an I/O activity that can be attacked or have it's trust model violated

Security aside it also adds more latency, and didn't a lot of people complain about revocation checking (the bespoke kind) adding unnecessary complexity and latency? Why would we accept it here for Vault and not there? (because Vault is a developer tool like k8s and we love it right? It's all hype and just like k8s hype as usually is complete nonsense when scrutinised).

Addressing the toxic anti-security / pro-tech toys, short aside; So we are accepting of complexity and latency when we add 'fun' things but as soon as a security requirement asks for the same thing it is a problem? Sounds like typical anti-security attitudes of uneducated and narrow-minded technical people that couldn't defend a sandwich from a fly let alone defend their decisions under scrutiny of actual security characteristics.

12 JWT Misconceptions

Our YUK WORDS tally is:

  • verify
  • integrity
  • claim
  • signature
  • assurance
  • trust
  • fail-closed
  • authorisation
  • ACL
  • threat
  • control
  • secrets

Every one of the above are words with defined meaning, and when people are discussing JWT (including the specs and guides) somehow forget the meaning at best, or misinterpret the security characteristics of the proper meaning.

If you are not educated about the security characteristic of each word, that's where you start before you try to use JWT, because the evidence shows that blindly following JWT guides is the root cause of there being wildly ineffective security characteristics associated with JWT implementations. It's widespread because the source of JWT over-complicates every security consideration for no apparent assurance purposes.

That statement is not an opinion, assurance is assurance. you have it and it is provable, or you don't and that's an inherent fact that exists whatever you say or don't say. Having no assurance is what we have before anything happens, and until we address that need for assurance we still don't have it, all we are doing is playing with toys, for fun. And that about summarises JWT, it's mostly fun, but we do get some integrity assurance and depending on certain choices we may get some other benefits that we are assured of but to be brutally honest we can get integrity actually very easily without JWT at all..

Summary

  • There are two JWT types; HMAC-based and Enc-based
  • Any JWT is used to transport claims (that are meaningless until validated) between interested parties
  • No user identity or entitlements are assured using the JWT verify function
  • Only a Enc-based JWT offers identity of the client guarantee to the server via the Private key held by the client
  • Either JWT type that uses private keys for Signatures can offer identity guarantees in either direction based on your implementation, it's not part of JWT itself
  • If using Enc-based JWT and the Signature is signed using a private key, you must have two separate private keys for each distinct operation, not use 1 key for both
  • JWTs that leverage X.509 Certificates for signing purposes offer revocation, not using X.509 means you either don't get revocation or you are adding bespoke revocations and probably didn't want JWT as it doesn't meet your needs if you're building bespoke solutions
  • JWT do not offer you any validation functions and therefore have no Authorisation guarantees
  • JWS and JWE are instances of the JWT offering distinct data that supports JWT itself and not intended to support your application logic
  • JWT tokens are intended to be transported over HTTPS (TLS) under the Authorization Bearer header (like OAuth 2.0) and is no more or less sensitive than any other header, so should not inherently be given any more trust than an API key exposed in a header
  • JWT is extremely complex, and complexity

Specifications