How to verify a JWT using python PyJWT with public key
I'm putting this here for the next person like me that looks for it.
What I needed was:
- A Private key that i can keep place behind a service (think AWS API GATEWAY) and generate JWT tokens securely and pass them down to lower services.
- A Public key that i can give to any of my micro services/anything else that can validate that the JWT token is valid WITHOUT knowing my Private key
Setup:
# lets create a key to sign these tokens with openssl genpkey -out mykey.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 # lets generate a public key for it... openssl rsa -in mykey.pem -out mykey.pub -pubout # make another key so we can test that we cannot decode from it openssl genpkey -out notmykey.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 # this is really the key we would be using to try to check the signature openssl rsa -in notmykey.pem -out notmykey.pub -pubout
Code:
import jwtfrom cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives import serialization# Load the key we createdwith open("mykey.pem", "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend() )# The data we're trying to pass along from place to placedata = {'user_id': 1}# Lets create the JWT token -- this is a byte array, meant to be sent as an HTTP headerjwt_token = jwt.encode(data, key=private_key, algorithm='RS256')print(f'data {data}')print(f'jwt_token {jwt_token}')# Load the public key to run another test...with open("mykey.pub", "rb") as key_file: public_key = serialization.load_pem_public_key( key_file.read(), backend=default_backend() )# This will prove that the derived public-from-private key is validprint(f'decoded with public key (internal): {jwt.decode(jwt_token, private_key.public_key())}')# This will prove that an external service consuming this JWT token can trust the token # because this is the only key it will have to validate the token.print(f'decoded with public key (external): {jwt.decode(jwt_token, public_key)}')# Lets load another public key to see if we can load the data successfulywith open("notmykey.pub", "rb") as key_file: not_my_public_key = serialization.load_pem_public_key( key_file.read(), backend=default_backend() )# THIS WILL FAIL!!!!!!!!!!!!!!!!!!!!!!!# Finally, this will not work and cause an exceptionprint(f'decoded with another public key: {jwt.decode(jwt_token, not_my_public_key)}')
More info here: https://gist.github.com/kingbuzzman/3912cc66896be0a06bf0eb23bb1e1999 -- along with a docker example of how to run this quickly
@javier-buzzi's answer returned this error to me:
TypeError: from_buffer() cannot return the address of a unicode object
Here is how I managed to make it work with python-jose
Create a RSA certificate (auth.pem) and it's public key (auth.pub):
openssl genpkey -out auth.pem -algorithm rsa -pkeyopt rsa_keygen_bits:2048 openssl rsa -in auth.pem -out auth.pub -pubout
(Thanks Javier)
from jose import jwtdata = { "sample" : "data"}# Encode datawith open("auth.pem") as key_file: token = jwt.encode(data, key=key_file.read(), algorithm='RS256')print(token)# Decode data with only he public keywith open("auth.pub") as pubkey_file: decoded_data = jwt.decode(token, key=pubkey_file.read(), algorithms='RS256')print(decoded_data)
output:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzYW1wbGUiOiJkYXRhIn0.GnDlS0FRFqdk1CsqFg2adHwSvrL8_JKtk4IQpuAzbjdDIi1xoymxxMIW4QNhl67QHIQrs0NG6lBi7eNfJ69Kgu6j-bY4NVP5-0D03wDrlBNowBPLMQ7RoCiDvtN1gqaTdf6VyNju6m9FmGImneZ84XMX2d1yWzXMSGtL2_8e99BmK0-h3r_o8IF7eSHN1SVxqrIN7vpcgfKcG0QjLZ-kBFpq4kgj5Fcr5coBIMmK6O0jB_4lBsNGa_0GixCXeWXkv_KqAky2yliEzV68lHOBCsBN_ZAjB3kllaIAOJCsQPLdqgXqgpeMQdzktVCVJKMAEYPdlv8mdadJSvxwxT9HBA{'sample': 'data'}
This other library (python-jose) may help verifying.
Notice that keys must be a JSON dict to be passed to decode
.