As a follow-up of my previous post on JWT authentication in Flask, I want to discuss the implications of using RS256 algorithm for signing the tokens with Flask-JWT library. First of all, what’s the difference between RS256 and HS256 (a standard one) algorithms for JWT?
- HS256 stands for HMAC with SHA-256. That’s an algorithm which encrypts and hashes the message (a JSON data in our case) at the same time using symmetrical secret key. The same key is used for encryption and decryption of the message.
- RS256 is an RSA encryption plus SHA-256 hashing. RSA is an asymmetric encryption algorithm, which means it operates on a pair of keys – public and private. Private key is used to encrypt a token, and public one – to decipher it. You can share the public key freely without compromising authentication scheme.
In a simple case, there might be no need to use RS256. However, if you want to validate tokens on client for any reason, for example, to protect against MITM attack (especially in case of no transport-level security), or to validate the client in a single sign-on scenario, RS256 is a right choice. Here’s how to configure Flask-JWT for that:
- Generate an RSA key pair with
openssl
openssl genrsa -out rs256.pem 2048 openssl rsa -in rs256.pem -pubout -outform PEM -out rs256.pub
- Install
cryptography
package which is not installed with Flask-JWT. Otherwise you’ll get
NotImplementedError: Algorithm not supported
- Configure RS256 in Flask settings
app.config['JWT_ALGORITHM'] = 'RS256' app.config['JWT_SECRET_KEY'] = open('rs256.pem').read() app.config['JWT_PUBLIC_KEY'] = open('rs256.pub').read()
- That should be it, however, Flask-JWT 0.3.2 has an implementation issue which would give
AttributeError: '_RSAPrivateKey' object has no attribute 'verifier'
with RS256 enabled. The reason is, it tries to use a private key for decryption instead of a public one. To fix that, you’ll need to supply your own jwt_decode_handler
at JWT initialization:
from flask import current_app import jwt as jwt_lib jwt = JWT() # JWT configuration code @jwt.jwt_decode_handler def rs256_jwt_decode_handler(token): secret = current_app.config['JWT_PUBLIC_KEY'] algorithm = current_app.config['JWT_ALGORITHM'] leeway = current_app.config['JWT_LEEWAY'] verify_claims = current_app.config['JWT_VERIFY_CLAIMS'] required_claims = current_app.config['JWT_REQUIRED_CLAIMS'] options = { 'verify_' + claim: True for claim in verify_claims } options.update({ 'require_' + claim: True for claim in required_claims }) return jwt_lib.decode(token, secret, options=options, algorithms=[algorithm], leeway=leeway)
With that, you’ll have JWT authorization working in a normal way, but now with RS256 JWTs: