How to Avoid JWT Collision While Receiving Bearer Token
I am doing a Django project where I am using JWT token for authentication. But the problem is that two different JWT tokens are both valid with the same signature that is provided in the backend with slight variation. What is the reason?
I also tried the implementation in FastAPI using PyJWT the result was kind a same where two different tokens were accepted by the backend server.
Valid Token from Backend
Other Forged Correct Tokens
Other Forged Incorrect Tokens
The last part of the JWT is the signature. Your JWT claims block shows you're using the HS256 signing algorithm, which corresponds to an HMAC signature using SHA-256 as the hash algorithm (RFC 7518 §3.2). The output of this hash is 256 bits, or 32 bytes, of raw binary data.
The three parts of the JWT are individually base64 encoded, using the URL-safe alphabet, with any padding trailing removed. Base64 encoding turns groups of 3 raw bytes into groups of 4 ASCII characters. With 32 bytes, you have 10 groups of 3 bytes, plus two more bytes. If you were using standard base64 encoding, you'd see the last group of bytes followed by an = pad character
$ openssl sha256 -binary /dev/null | openssl base64
47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
Since each base64 output character can hold 64 different values, it encodes 6 bits; so 3 output characters can hold 18 bits. Meanwhile in your 2 extra input bytes you have 16 bits. That's two extra unused bits at the end. That's where the c, d, and e (and also f) will generate the same signature when you base64 decode it. Again, you can play with this at the command line:
echo AAc= | openssl base64 -d | od -t x1
produces the two-byte output sequence 00 07, but so will AAd=, AAe=, and AAf=; but not AAb= or AAg=.
This is the only possible modification to the JWT (unless you're extremely lucky, on a "lightning struck twice and destroyed my winning lottery tickets both times" scale). Changing one of the earlier parts will invalidate the signature; any other change will change the signature value and it won't match the body.
If you're trying to cache something based on the exact JWT value, you're probably fine using the JWT as a key in a dictionary. It's theoretically possible that someone might try to convey some information in these two unused bits but it's probably not a practical concern. If you're only trying to do this to avoid the cryptographic operation and to cache details of known-valid JWTs, you at the very worst have 4 copies of each token. If this still bothers you, you can manually force the value of these two bits, or unconditionally validate the token and cache based on some JWT claim (e.g. ,the sub user ID claim).




