On my exploration of JSON Web Tokens (JWT) I’m having some fun creating a basic Flask (Python) webapp that uses JWTs thanks to the ‘Flask JWT Extended‘. I’ve created a page that shows the encoded JWT Header, Payload and Signature, and then shows the decoded JWT Header and Payload. It was all working great until I added some options for JWT additional claims and then…. “binascii.Error: Incorrect padding”. What broke? Had I done the additional claims wrong?

I broke the JWT down into its three components using the split option of strings in Python (separating on the “.”). I then used Python’s Base64 library and to handle the decoding, with the incorrect padding error occurring on the JWT Payload, but only when my additional claims are being used.

Taking a look at the JWT Payload and at first glance the encoded string looks okay:

jwt_payload = "eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc2NDI2NTcyNiwianRpIjoiM2EyZTAwNjgtYTIxYi00MWQxLTk2NDYtNmM5YzQ3MjQyMTNlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjIiLCJuYmYiOjE3NjQyNjU3MjYsImNzcmYiOiI5NzNiZTczNS03NjE4LTRkNTMtOTBiNS02NzM5MTI1ZjAxNjQiLCJleHAiOjE3NjQyNjY2MjYsImZvbyI6ImJhciIsIjEiOiIyIn0"

But on closer inspection, and with the help of len (returns the length of an object), the jwt_payload is 271 characters long.

Base64 : Padding and Multiplication of 4

When decoding the jwt_payload, base64.b64decode is expecting an object that is divisible by 4 with 0 remainders.

decoded_bytes = base64.b64decode(jwt_payload)

binascii.Error: Incorrect padding

271 is not divisible by 4 with out giving a remainder, and base64.b64decode then sees this object as having incorrect padding. There are other reasons padding errors occur, but in this case it is that 271 is not in the 4x multiplication tables.

A quick confirmation of this can be achieved by adding a padding character (‘=’). Wikipedia goes into detail on what padding is in Base64.

jwt_payload = "eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc2NDI2NTcyNiwianRpIjoiM2EyZTAwNjgtYTIxYi00MWQxLTk2NDYtNmM5YzQ3MjQyMTNlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjIiLCJuYmYiOjE3NjQyNjU3MjYsImNzcmYiOiI5NzNiZTczNS03NjE4LTRkNTMtOTBiNS02NzM5MTI1ZjAxNjQiLCJleHAiOjE3NjQyNjY2MjYsImZvbyI6ImJhciIsIjEiOiIyIn0="

print(len(jwt_payload))

decoded_bytes = base64.b64decode(jwt_payload)

decoded_ascii = base64.b64decode(jwt_payload).decode("ascii")

print(decoded_ascii)

This time jwt_payload is 272 characters and it decodes correctly to:

{"fresh":false,"iat":1764265726,"jti":"3a2e0068-a21b-41d1-9646-6c9c4724213e","type":"access","sub":"2","nbf":1764265726,"csrf":"973be735-7618-4d53-90b5-6739125f0164","exp":1764266626,"foo":"bar","1":"2"}

With my additional claims of “foo”:”bar” and “1”:”2″ correctly in place (at this stage I’m playing with additional claims). Note: This shows that anything placed in the JWT payload although encoded is publicly available to anyone that receives the token, and it can be decoded quite easily.

Some Python To Resolve

Manually adding padding (“=”) to objects is not really a solution outside of testing what may be causing the incorrect padding error. So I threw some Python functions together to carry out the padding for me.

import base64

jwt_payload = "eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc2NDI2NTcyNiwianRpIjoiM2EyZTAwNjgtYTIxYi00MWQxLTk2NDYtNmM5YzQ3MjQyMTNlIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6IjIiLCJuYmYiOjE3NjQyNjU3MjYsImNzcmYiOiI5NzNiZTczNS03NjE4LTRkNTMtOTBiNS02NzM5MTI1ZjAxNjQiLCJleHAiOjE3NjQyNjY2MjYsImZvbyI6ImJhciIsIjEiOiIyIn0"

def correct_padding(jwt_payload):
    jwt_payload = jwt_payload
    padding_needed = len(jwt_payload) % 4
    i = 0
    while i < padding_needed:
        jwt_payload = jwt_payload+"="
        i+=1
    return jwt_payload

def decode_jwt_payload(jwt_payload):
    jwt_payload = jwt_payload
    corrected_payload = correct_padding(jwt_payload)
    decoded_bytes = base64.b64decode(corrected_payload)
    decoded_ascii = base64.b64decode(corrected_payload).decode("ascii")
    return (decoded_ascii)

print(decode_jwt_payload(jwt_payload))

With a quick print to make sure that the JWT payload is as expected, I can now use the functions in my Flask site to handle the decoding.