pyjwt - Documentation

What is PyJWT?

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.

Why use PyJWT?

PyJWT offers several advantages for handling JWTs in Python applications:

Installation and Setup

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).

Basic Usage Example

This example demonstrates creating and verifying a JWT using the HS256 algorithm:

import jwt

# Secret key – keep this secure!  Never hardcode this in production.
secret_key = 'your-256-bit-secret'

# Payload data
payload = {
    'user_id': 123,
    'username': 'johndoe',
    'exp': jwt.time() + 3600  # Token expires in 1 hour
}

# Encode the JWT
encoded_jwt = jwt.encode(payload, secret_key, algorithm='HS256')
print(f"Encoded JWT: {encoded_jwt}")

# Decode the JWT
try:
    decoded_payload = jwt.decode(encoded_jwt, secret_key, algorithms=['HS256'])
    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.

Encoding JSON Web Tokens (JWTs)

Understanding JWT Structure

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 (.):

  1. Header: Contains metadata about the token, including the algorithm used for signing.
  2. Payload: Carries the claims, which are statements about an entity (e.g., user identity).
  3. Signature: A cryptographically generated string that ensures the token’s integrity and authenticity.

Header Parameters

The JWT header is a JSON object typically containing:

Other parameters can be added depending on the specific needs of the application.

Payload Claims

The JWT payload is a JSON object carrying the claims. These claims can be custom, but some standard claims are commonly used:

Signature Algorithm Selection

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.

Example: Encoding a JWT with HS256

import jwt

secret_key = 'your-256-bit-secret'  # MUST be replaced with a strong, randomly generated secret

payload = {
    'user_id': 123,
    'username': 'johndoe',
    'exp': jwt.time() + 3600
}

encoded_jwt = jwt.encode(payload, secret_key, algorithm='HS256')
print(encoded_jwt)

Example: Encoding a JWT with RS256

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!)
private_key = rsa.generate_private_key(
    public_exponent=65537, key_size=2048, backend=default_backend()
)
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),
)

payload = {
    'user_id': 123,
    'username': 'johndoe',
    'exp': jwt.time() + 3600
}


encoded_jwt = jwt.encode(payload, private_pem, algorithm='RS256')
print(encoded_jwt)

Example: Encoding a JWT with ES256

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!)
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),
)


payload = {
    'user_id': 123,
    'username': 'johndoe',
    'exp': jwt.time() + 3600
}

encoded_jwt = jwt.encode(payload, private_pem, algorithm='ES256')
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 JSON Web Tokens (JWTs)

Decoding Process

Decoding a JWT involves three main steps:

  1. Splitting the Token: The JWT string is split into its three parts (header, payload, signature) using the dot (.) as a delimiter.
  2. Verifying the Signature: The signature is verified using the provided key and algorithm specified in the header. This step ensures the token’s integrity and authenticity; that it hasn’t been tampered with.
  3. Decoding the Payload: If the signature is valid, the payload (the JSON data) is decoded and returned.

Verification of Signature

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.

Handling Signature Verification Errors

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.

Extracting Payload Claims

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.

Example: Decoding a JWT with HS256

import jwt

secret_key = 'your-256-bit-secret'  # MUST be replaced with the SAME secret key used for encoding

encoded_jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiam9obmRvZSIsImV4cCI6MTY3ODc2NzQ3MX0.e5h3e5f4gS3G-f4f7gF_a5e_e5f4gS3G-f4f7gF_a5e" #Example JWT, replace with your encoded JWT

try:
    decoded_payload = jwt.decode(encoded_jwt, secret_key, algorithms=['HS256'])
    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}")

Example: Decoding a JWT with RS256

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:
    public_key = serialization.load_pem_public_key(
        key_file.read(), backend=default_backend()
    )

encoded_jwt = "your_rs256_encoded_jwt" #replace with your RS256 encoded JWT


try:
    decoded_payload = jwt.decode(encoded_jwt, public_key, algorithms=['RS256'])
    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}")

Example: Decoding a JWT with ES256

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:
    public_key = serialization.load_pem_public_key(
        key_file.read(), backend=default_backend()
    )

encoded_jwt = "your_es256_encoded_jwt" #replace with your ES256 encoded JWT

try:
    decoded_payload = jwt.decode(encoded_jwt, public_key, algorithms=['ES256'])
    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.

Dealing with Expired Tokens

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.

Working with Keys

Symmetric Keys (HS256)

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):

secret_key = "your-256-bit-secret" #This is INSECURE! Replace with a proper key generation method.

Secure Key Generation: Use a cryptographically secure random number generator to create your keys. Libraries like secrets (Python 3.6+) are recommended:

import secrets
secret_key = secrets.token_hex(32) # 32 bytes = 256 bits

Important: Never hardcode keys directly into your application code. Use environment variables, configuration files, or dedicated secret management services.

Asymmetric Keys (RS256, ES256)

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
private_key = rsa.generate_private_key(
    public_exponent=65537, key_size=2048, backend=default_backend()
)
public_key = private_key.public_key()

# ECDSA key pair generation
private_key_ec = ec.generate_private_key(ec.SECP256R1(), default_backend())
public_key_ec = private_key_ec.public_key()

# Serializing keys to PEM format for storage.
# Always use appropriate encryption for private key storage in production
private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),  #INSECURE - Use a proper encryption algorithm!
)

public_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

Generating Key Pairs

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.

Storing and Managing Keys Securely

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.

Key Rotation

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.

Advanced Usage

Custom Claims

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)

Algorithm Configuration

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.

encoded_jwt = jwt.encode(payload, key, algorithm='RS256')  #Explicit algorithm specification

decoded_payload = jwt.decode(encoded_jwt, key, algorithms=['RS256']) #Explicit algorithm verification

JWT Header Customization

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
}

encoded_jwt = jwt.encode(payload, key, algorithm='HS256', headers=header)

Working with Different Key Formats (PEM, JWK)

PyJWT can work with keys in various formats:

import jwt

# Example JWK for RSA
jwk = {
  "kty": "RSA",
  "kid": "my-key-id",
  "use": "sig",
  "n": "your_modulus",
  "e": "your_exponent",
  # ... other JWK parameters
}

decoded_payload = jwt.decode(encoded_jwt, jwk, algorithms=['RS256'])

Using PyJWT with Frameworks (e.g., Flask, Django)

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

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'  #Store key securely in production

@app.route('/protected')
def protected():
    try:
        token = request.headers['Authorization'].split()[1]  #Extract token from header
        decoded = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        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.

Troubleshooting Common Errors

Common PyJWT errors and solutions:

Performance Considerations and Optimization

For high-throughput applications, consider these optimizations:

Security Best Practices

Secure Key Management

The security of your JWTs hinges on the security of your keys. Follow these practices:

Protecting Against Attacks

JWTs are relatively secure when used correctly, but be aware of potential attacks:

Choosing Appropriate Algorithms

Select algorithms based on your security requirements and performance needs:

Avoid weak or deprecated algorithms.

Regular Updates and Patches

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.

Input Validation and Sanitization

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:

Appendix

Supported Algorithms

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.

Error Codes and Exceptions

PyJWT raises exceptions to signal errors during JWT encoding and decoding. Key exceptions include:

Consult the PyJWT documentation for a more complete list and details on specific error conditions. Appropriate error handling is crucial for robust application design.

Glossary of Terms

References and Further Reading

This appendix provides a concise overview. Refer to the linked resources for more comprehensive information.