How to sign and verify JWT RS256 in Python
Generate RSA keys, create a JWT signed with RS256, verify its signature, and decode the payload using the cryptography library.
pip install cryptography
Python code
67 linesimport json
import time
import base64
import hmac
import hashlib
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature, decode_dss_signature
# Generate RSA key pair
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
# Encode private/public keys to PEM for realistic usage
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
def b64url_encode(data):
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
def b64url_decode(data):
padding = '=' * (4 - len(data) % 4)
return base64.urlsafe_b64decode(data + padding)
def create_jwt(payload, private_pem):
header = {"alg": "RS256", "typ": "JWT"}
header_b64 = b64url_encode(json.dumps(header).encode())
payload_b64 = b64url_encode(json.dumps(payload).encode())
signing_input = f"{header_b64}.{payload_b64}".encode()
key = serialization.load_pem_private_key(private_pem, password=None)
signature = key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
return f"{header_b64}.{payload_b64}.{b64url_encode(signature)}"
def verify_jwt(token, public_pem):
header_b64, payload_b64, signature_b64 = token.split('.')
signing_input = f"{header_b64}.{payload_b64}".encode()
key = serialization.load_pem_public_key(public_pem)
try:
key.verify(b64url_decode(signature_b64), signing_input, padding.PKCS1v15(), hashes.SHA256())
payload = json.loads(b64url_decode(payload_b64))
return True, payload
except Exception:
return False, None
if __name__ == "__main__":
payload = {"sub": "1234567890", "name": "Alice", "iat": int(time.time())}
token = create_jwt(payload, private_pem)
print(f"JWT: {token}")
valid, decoded = verify_jwt(token, public_pem)
print(f"Valid: {valid}")
print(f"Decoded payload: {decoded}")
# Test tampered token
tampered = token[:-4] + "xxxx"
valid, decoded = verify_jwt(tampered, public_pem)
print(f"Tampered valid: {valid}")
Output
JWT: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE5MDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Valid: True
Decoded payload: {'sub': '1234567890', 'name': 'Alice', 'iat': 1719000000}
Tampered valid: False
How it works
The code generates an RSA key pair and encodes them to PEM strings, mimicking a real deployment where keys are stored or distributed. create_jwt builds the header and payload, base64url-encodes them, then signs the concatenated string using PKCS1v15 padding with SHA-256 — the RS256 algorithm. verify_jwt splits the token, re-creates the signing input, and uses the public key to verify the signature. Any tampering or invalid signature raises an exception, which is caught to return False. The payload is decoded only after successful verification, ensuring data integrity.
Common mistakes
- Forgetting to add base64 padding when decoding a URL-safe encoded segment
- Using the private key for verification instead of the public key
- Not catching exceptions during verification, causing crashes on invalid tokens
- Hardcoding keys in source code instead of loading from secure storage
Variations
- Use PyJWT library to sign and verify in one line: `jwt.encode(payload, private_key, algorithm='RS256')`
- Load keys from files using `serialization.load_pem_private_key` with passwords
Real-world use cases
- Issuing access tokens in an OAuth2 authorization server after user login.
- Verifying JWTs received by an API from a separate authentication service.
- Implementing signed session tokens for microservices to trust inter-service calls.
Sponsored
More from Auth & security at scale
- ACME LetsEncrypt Mock Challenge Server in Python medium
- AES GCM encryption and decryption in Python medium
- Build a Mock OIDC Userinfo Endpoint in Python with Flask easy
- ChaCha20-Poly1305 mock in Python medium
- ECDH key agreement in Python with cryptography medium
- Enforce TLS 1.2 Minimum in Python easy
Keep learning
Related tutorials and quizzes for this topic.