Back to Blog
Python

Using PyJWT to Sign and Verify RSA Tokens in Python

python pyjwt rsa signed tokens: Learn how to create and verify RS256 signed JWT tokens with PyJWT in Python, including key generation, error handling, and production s...

PyJWTJWTRSARS256Token SigningPython Security
A stylized illustration of a lock and key representing RSA public and private keys used to sign and verify JWT tokens in Python.

When you need to sign JSON Web Tokens with RSA in Python, PyJWT is the library most developers reach for. The python pyjwt rsa signed tokens workflow is straightforward, but it has a few sharp edges around key formats, algorithm selection, and error handling that can trip you up in production.

This article walks through the complete flow: generating an RSA key pair, encoding a token with the private key, decoding it with the public key, and handling the failures that occur when keys or algorithms don't match.

Installing PyJWT and the Cryptography Dependency

PyJWT supports RSA signing through the cryptography library. You need both packages to work with RS256 tokens. Install them with pip:

pip install pyjwt cryptography

The cryptography package provides the RSA key loading and serialization primitives that PyJWT relies on. Without it, PyJWT raises an error when you attempt to use an RSA algorithm.

Generating an RSA Key Pair

You can generate an RSA key pair with the cryptography library directly in Python, or use OpenSSL from the command line. The key must be in PEM format for PyJWT to read it.

Using OpenSSL:

openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem

Using Python with cryptography:

from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), ) public_pem = private_key.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, )

In a real application you typically store the private key in a secure location and load it at runtime. The public key can be distributed to services that need to verify tokens.

Creating an RS256 Signed Token

PyJWT's encode method takes a payload, a key, and an algorithm. For RS256, the key must be the private key in PEM format.

import jwt payload = { "sub": "1234567890", "name": "Jane Doe", "iat": 1516239022, } private_key = open("private.pem").read() token = jwt.encode(payload, private_key, algorithm="RS256") print(token)

The encode method returns a string token. PyJWT automatically adds the alg header and the signature. The private key is used to sign the header and payload.

If you pass a public key instead of a private key, PyJWT raises an error because it cannot sign with a public key.

Verifying an RS256 Token

Verification uses the public key. The decode method checks the signature and, by default, verifies the expiration time if the payload contains an exp claim.

import jwt public_key = open("public.pem").read() token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." try: decoded = jwt.decode(token, public_key, algorithms=["RS256"]) print(decoded) except jwt.InvalidTokenError as e: print(f"Invalid token: {e}")

Always specify the algorithms parameter. If you omit it, PyJWT uses the algorithm from the token header, which opens the door to algorithm confusion attacks. An attacker could send a token signed with HS256 and trick the server into using the public key as a symmetric secret.

Common Errors and Their Causes

RSA token verification often fails with cryptic errors. Here are the ones you're most likely to see:

ErrorCauseFix
DecodeError: Not enough segmentsToken is malformed or truncatedCheck that the token string is complete and has three dot-separated parts
InvalidKeyError: The specified key is an invalid format for RSAKey is not a valid PEM RSA keyEnsure the key is in PEM format and contains the correct header (BEGIN PUBLIC KEY or BEGIN PRIVATE KEY)
InvalidAlgorithmError: The specified alg value is not allowedThe algorithm in the token header does not match the allowed listPass algorithms=["RS256"] and confirm the token was signed with RS256
Signature verification failedThe public key does not match the private key that signed the tokenVerify you're using the correct public key and that the key pair is a matching set
ExpiredSignatureErrorThe token's exp claim is in the pastCheck the system clock and the token's expiration time

When you catch jwt.InvalidTokenError, it covers all of these subclasses. For more granular handling, catch specific exceptions like jwt.ExpiredSignatureError before the general one.

Key Management and Security Considerations

The private key is the most sensitive part of an RS256 setup. If it leaks, anyone can forge tokens. Store it in a secrets manager, a hardware security module, or at least in a file with restricted permissions. Never commit it to version control.

The public key can be shared freely, but you need a trusted way to distribute it. If a service fetches the public key from a remote endpoint, use HTTPS and consider pinning the key or using a JWKS (JSON Web Key Set) endpoint for rotation.

Algorithm confusion is a real threat. Always restrict the allowed algorithms on the verification side. Never let the token header dictate which algorithm is used for verification.

Handling Token Expiration and Clock Skew

PyJWT verifies the exp claim by default. If the token is expired, it raises ExpiredSignatureError. You can add a small leeway to accommodate clock differences between the issuing and verifying servers:

decoded = jwt.decode(token, public_key, algorithms=["RS256"], leeway=30)

The leeway parameter accepts seconds or a timedelta. Use it only when clock synchronization is a genuine concern; too much leeway shortens the effective token lifetime.

Performance and Operational Notes

RSA signature verification is computationally heavier than HMAC. For high-throughput services, verify tokens once and cache the result, or use a token introspection endpoint if you have one. The public key itself can be cached in memory to avoid repeated file reads.

If you need to verify many tokens per second, consider using RS256 only for external or cross-service authentication, and use HS256 with a shared secret for internal microservice communication where the secret can be managed centrally. The choice depends on your threat model and key distribution constraints.

When rotating keys, keep the old public key available for a grace period so tokens signed with the previous key can still be verified. A JWKS endpoint that lists multiple keys is the standard way to handle this.

python pyjwt rsa signed tokens: Sign & Verify RS256 | RYUSLOG DEV