PyJWT is a Python library that implements JSON Web Token (JWT) creation and verification. JWTs are a compact and self-contained way to securely transmit information between parties as a JSON object. PyJWT provides functions for encoding and decoding JWTs, allowing developers to integrate JWT authentication and authorization into their applications. It supports various signing algorithms, ensuring flexibility and security depending on your needs.
PyJWT offers several advantages for handling JWTs in Python applications:
PyJWT can be easily installed using pip:
pip install PyJWT
No further setup is generally required. However, if you’re using algorithms like RSA, you’ll need to ensure you have the necessary cryptographic libraries installed (usually handled automatically by PyJWT’s dependencies).
This example demonstrates creating and verifying a JWT using the HS256 algorithm:
import jwt
# Secret key – keep this secure! Never hardcode this in production.
= 'your-256-bit-secret'
secret_key
# Payload data
= {
payload 'user_id': 123,
'username': 'johndoe',
'exp': jwt.time() + 3600 # Token expires in 1 hour
}
# Encode the JWT
= jwt.encode(payload, secret_key, algorithm='HS256')
encoded_jwt print(f"Encoded JWT: {encoded_jwt}")
# Decode the JWT
try:
= jwt.decode(encoded_jwt, secret_key, algorithms=['HS256'])
decoded_payload print(f"Decoded Payload: {decoded_payload}")
except jwt.ExpiredSignatureError:
print("Signature has expired.")
except jwt.InvalidTokenError:
print("Invalid token.")
except Exception as e:
print(f"An error occurred: {e}")
Remember to replace "your-256-bit-secret"
with a strong, randomly generated secret key. Never commit this key to version control. For production environments, consider using more secure key management practices.
A JSON Web Token (JWT) is a compact, URL-safe method for representing claims to be transferred between parties. It consists of three parts separated by dots (.
):
The JWT header is a JSON object typically containing:
"alg"
: The algorithm used to create the signature (e.g., “HS256”, “RS256”, “ES256”). This is crucial for verification."typ"
: Usually set to "JWT"
, indicating the token type. While optional, it’s best practice to include it.Other parameters can be added depending on the specific needs of the application.
The JWT payload is a JSON object carrying the claims. These claims can be custom, but some standard claims are commonly used:
"iss"
: Issuer of the token."sub"
: Subject (the entity the token is about)."aud"
: Audience(s) the token is intended for."exp"
: Expiration time (Unix timestamp)."nbf"
: Not Before (Unix timestamp)."iat"
: Issued At (Unix timestamp)."jti"
: JWT ID (unique identifier for the token).The choice of signing algorithm significantly impacts the security of your JWTs:
Other algorithms are also supported by PyJWT, but the above are the most common.
import jwt
= 'your-256-bit-secret' # MUST be replaced with a strong, randomly generated secret
secret_key
= {
payload 'user_id': 123,
'username': 'johndoe',
'exp': jwt.time() + 3600
}
= jwt.encode(payload, secret_key, algorithm='HS256')
encoded_jwt print(encoded_jwt)
import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# Generate an RSA key pair (replace with your existing keys in production!)
= rsa.generate_private_key(
private_key =65537, key_size=2048, backend=default_backend()
public_exponent
)= private_key.private_bytes(
private_pem =serialization.Encoding.PEM,
encodingformat=serialization.PrivateFormat.PKCS8,
=serialization.NoEncryption(),
encryption_algorithm
)
= {
payload 'user_id': 123,
'username': 'johndoe',
'exp': jwt.time() + 3600
}
= jwt.encode(payload, private_pem, algorithm='RS256')
encoded_jwt print(encoded_jwt)
import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
# Generate an EC key pair (replace with your existing keys in production!)
= ec.generate_private_key(ec.SECP256R1(), default_backend())
private_key = private_key.private_bytes(
private_pem =serialization.Encoding.PEM,
encodingformat=serialization.PrivateFormat.PKCS8,
=serialization.NoEncryption(),
encryption_algorithm
)
= {
payload 'user_id': 123,
'username': 'johndoe',
'exp': jwt.time() + 3600
}
= jwt.encode(payload, private_pem, algorithm='ES256')
encoded_jwt print(encoded_jwt)
Note: For RS256 and ES256, replace the key generation with your actual private key loading from a secure location. Never hardcode private keys in your application code. The examples above are for illustrative purposes only. Always use proper key management practices in a production environment.
Decoding a JWT involves three main steps:
.
) as a delimiter.Signature verification is crucial to ensure the JWT hasn’t been altered. PyJWT uses the provided key and algorithm to check the signature’s validity. If the verification fails, it indicates the token might be compromised. The jwt.decode()
function handles this verification automatically.
If the signature verification fails, PyJWT raises a jwt.InvalidSignatureError
or a jwt.InvalidTokenError
. Your application should handle these exceptions gracefully, typically by rejecting the token and taking appropriate action (e.g., logging the error, redirecting the user to a login page). Consider logging suspicious activity for security auditing.
Once the signature is verified, the payload, a Python dictionary, is accessible and can be used to extract the claims. These claims contain the information carried by the JWT.
import jwt
= 'your-256-bit-secret' # MUST be replaced with the SAME secret key used for encoding
secret_key
= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiam9obmRvZSIsImV4cCI6MTY3ODc2NzQ3MX0.e5h3e5f4gS3G-f4f7gF_a5e_e5f4gS3G-f4f7gF_a5e" #Example JWT, replace with your encoded JWT
encoded_jwt
try:
= jwt.decode(encoded_jwt, secret_key, algorithms=['HS256'])
decoded_payload print(decoded_payload)
except jwt.ExpiredSignatureError:
print("Signature has expired.")
except jwt.InvalidTokenError:
print("Invalid token.")
except jwt.InvalidSignatureError:
print("Invalid signature.")
except Exception as e:
print(f"An error occurred: {e}")
import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# Load your public key (replace with your actual public key loading)
with open("public_key.pem", "rb") as key_file:
= serialization.load_pem_public_key(
public_key =default_backend()
key_file.read(), backend
)
= "your_rs256_encoded_jwt" #replace with your RS256 encoded JWT
encoded_jwt
try:
= jwt.decode(encoded_jwt, public_key, algorithms=['RS256'])
decoded_payload print(decoded_payload)
except jwt.ExpiredSignatureError:
print("Signature has expired.")
except jwt.InvalidTokenError:
print("Invalid token.")
except jwt.InvalidSignatureError:
print("Invalid signature.")
except Exception as e:
print(f"An error occurred: {e}")
import jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
# Load your public key (replace with your actual public key loading)
with open("public_key.pem", "rb") as key_file:
= serialization.load_pem_public_key(
public_key =default_backend()
key_file.read(), backend
)
= "your_es256_encoded_jwt" #replace with your ES256 encoded JWT
encoded_jwt
try:
= jwt.decode(encoded_jwt, public_key, algorithms=['ES256'])
decoded_payload print(decoded_payload)
except jwt.ExpiredSignatureError:
print("Signature has expired.")
except jwt.InvalidTokenError:
print("Invalid token.")
except jwt.InvalidSignatureError:
print("Invalid signature.")
except Exception as e:
print(f"An error occurred: {e}")
Remember to replace placeholder JWTs and keys with your actual values. For RS256 and ES256, you’ll need the public key corresponding to the private key used for encoding.
JWTs often include an expiration time (exp
claim). PyJWT automatically checks this claim during decoding. If the token is expired, it raises a jwt.ExpiredSignatureError
. Your application should handle this exception appropriately, typically by rejecting the token and prompting the user to re-authenticate.
For algorithms like HS256 (HMAC SHA256), a symmetric key is used. This means the same secret key is used for both signing (encoding) and verifying (decoding) the JWT. This key must be kept absolutely secret and secure. Compromising this key compromises the security of all JWTs signed with it.
Key Requirements: For HS256, the key should be a 256-bit (or longer) randomly generated string. Never use a weak or predictable key.
Example (Insecure for demonstration only - NEVER use this in production):
= "your-256-bit-secret" #This is INSECURE! Replace with a proper key generation method. secret_key
Secure Key Generation: Use a cryptographically secure random number generator to create your keys. Libraries like secrets
(Python 3.6+) are recommended:
import secrets
= secrets.token_hex(32) # 32 bytes = 256 bits secret_key
Important: Never hardcode keys directly into your application code. Use environment variables, configuration files, or dedicated secret management services.
Algorithms like RS256 (RSA SHA256) and ES256 (ECDSA SHA256) use asymmetric key cryptography. This involves a pair of keys: a private key for signing and a public key for verification. The private key must be kept extremely secure; the public key can be distributed widely.
Key Requirements: The key pair should be generated using a cryptographically secure method, with appropriate key sizes (e.g., 2048 bits for RSA, SECP256R1 for ECDSA).
Example (Insecure for demonstration only):
The following shows key generation; in a real application, you would load these from secure storage.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec
# RSA key pair generation
= rsa.generate_private_key(
private_key =65537, key_size=2048, backend=default_backend()
public_exponent
)= private_key.public_key()
public_key
# ECDSA key pair generation
= ec.generate_private_key(ec.SECP256R1(), default_backend())
private_key_ec = private_key_ec.public_key()
public_key_ec
# Serializing keys to PEM format for storage.
# Always use appropriate encryption for private key storage in production
= private_key.private_bytes(
private_pem =serialization.Encoding.PEM,
encodingformat=serialization.PrivateFormat.PKCS8,
=serialization.NoEncryption(), #INSECURE - Use a proper encryption algorithm!
encryption_algorithm
)
= public_key.public_bytes(
public_pem =serialization.Encoding.PEM,
encodingformat=serialization.PublicFormat.SubjectPublicKeyInfo,
)
The examples above demonstrate key generation using the cryptography
library. Other libraries might offer similar functionality. Always ensure you are using a cryptographically secure method for key generation.
Never store keys directly in your source code or easily accessible files. Use secure methods like:
When storing private keys, always use strong encryption to protect them from unauthorized access. serialization.NoEncryption()
shown above is highly insecure and only for illustrative purposes.
Regularly rotating your keys is a crucial security practice. This limits the impact of a potential key compromise. The frequency of rotation depends on your risk tolerance and security requirements. Consider factors like:
When rotating keys, plan a transition period to ensure backward compatibility. You might need to handle both old and new keys for a certain time.
Beyond the standard claims, you can add any custom claims to the JWT payload to carry application-specific information. These claims are defined by your application and are not standardized.
import jwt
= {
payload 'user_id': 123,
'username': 'johndoe',
'exp': jwt.time() + 3600,
'role': 'administrator', # Custom claim
'department': 'engineering' #Custom claim
}
# ... (encoding and decoding as before)
PyJWT supports various algorithms. You can explicitly specify the algorithm during encoding and decoding to ensure compatibility. The algorithms
parameter in jwt.decode()
is crucial for security; it verifies that the token was signed with an expected algorithm. Only specifying algorithms you trust prevents algorithm switching attacks.
= jwt.encode(payload, key, algorithm='RS256') #Explicit algorithm specification
encoded_jwt
= jwt.decode(encoded_jwt, key, algorithms=['RS256']) #Explicit algorithm verification decoded_payload
While less common, you can add custom parameters to the JWT header beyond alg
and typ
. These parameters are included in the encoded token but are typically not directly used for verification.
import jwt
= {
header "alg": "HS256",
"typ": "JWT",
"kid": "my-key-id" #Custom header parameter
}
= jwt.encode(payload, key, algorithm='HS256', headers=header) encoded_jwt
PyJWT can work with keys in various formats:
PEM (Privacy Enhanced Mail): A common format for storing public and private keys, often used with RSA and ECDSA. The examples in previous sections used PEM format.
JWK (JSON Web Key): A JSON-based format for representing cryptographic keys. PyJWT provides support for loading keys from JWK dictionaries.
import jwt
# Example JWK for RSA
= {
jwk "kty": "RSA",
"kid": "my-key-id",
"use": "sig",
"n": "your_modulus",
"e": "your_exponent",
# ... other JWK parameters
}
= jwt.decode(encoded_jwt, jwk, algorithms=['RS256']) decoded_payload
PyJWT integrates easily into various web frameworks. You can use it as a part of your authentication and authorization logic. Here’s a simplified example using Flask:
from flask import Flask, request, jsonify
import jwt
= Flask(__name__)
app 'SECRET_KEY'] = 'your-secret-key' #Store key securely in production
app.config[
@app.route('/protected')
def protected():
try:
= request.headers['Authorization'].split()[1] #Extract token from header
token = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
decoded return jsonify({'message': 'Protected resource accessed'}), 200
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token'}), 401
except Exception as e:
return jsonify({'message': 'An error occurred'}), 500
Adapt this example to your framework’s authentication mechanisms. Remember to securely manage your secret key.
Common PyJWT errors and solutions:
jwt.ExpiredSignatureError
: The token’s expiration time (exp
claim) has passed. Check the token’s expiry and ensure your system clock is synchronized.jwt.InvalidSignatureError
: The signature is invalid. Verify that you’re using the correct key and algorithm. Ensure that the key hasn’t been compromised.jwt.InvalidTokenError
: The token is malformed or otherwise invalid. Check for any structural problems in the token string.jwt.DecodeError
: The token cannot be decoded. Verify the token is correctly formatted.For high-throughput applications, consider these optimizations:
The security of your JWTs hinges on the security of your keys. Follow these practices:
secrets.token_hex()
in Python) to generate keys of sufficient length.JWTs are relatively secure when used correctly, but be aware of potential attacks:
algorithms
parameter in jwt.decode()
is correctly configured to only allow the algorithms you expect and trust. Never allow a token to specify the algorithm itself.jti
(JWT ID) claims to prevent replay attacks where an attacker reuses a valid token. Also, ensure your system handles token expiration and revocation effectively.Select algorithms based on your security requirements and performance needs:
Avoid weak or deprecated algorithms.
Keep PyJWT and its dependencies up to date to benefit from bug fixes and security patches. Regular updates mitigate vulnerabilities that might be exploited by attackers.
Always validate and sanitize any input data before using it to generate or decode JWTs. This prevents injection attacks and other vulnerabilities. Never trust user-provided data without proper validation:
PyJWT supports a range of JSON Web Signature (JWS) and JSON Web Encryption (JWE) algorithms. The exact set of supported algorithms may vary slightly depending on the installed dependencies (specifically cryptographic libraries). However, commonly supported algorithms include:
For JWE algorithms, PyJWT supports various key management and encryption methods (e.g., A128CBC-HS256, A256GCM). Refer to the PyJWT documentation and the relevant RFCs for a complete and up-to-date list of supported algorithms.
PyJWT raises exceptions to signal errors during JWT encoding and decoding. Key exceptions include:
jwt.DecodeError
: A general error during token decoding, often indicating a malformed token.jwt.ExpiredSignatureError
: The token’s exp
(expiration) claim has passed.jwt.InvalidAudienceError
: The token’s aud
(audience) claim doesn’t match the expected audience.jwt.InvalidIssuedAtError
: The token’s iat
(issued at) claim is invalid.jwt.InvalidIssuedAtError
: The token’s nbf
(not before) claim is invalid.jwt.InvalidSignatureError
: The token’s signature is invalid.jwt.InvalidTokenError
: A general error indicating an invalid token structure or format.Consult the PyJWT documentation for a more complete list and details on specific error conditions. Appropriate error handling is crucial for robust application design.
cryptography
).This appendix provides a concise overview. Refer to the linked resources for more comprehensive information.