joserfc-wrapper is a library for easy use of JWT and automatic management of signature keys.
Project description
The joserfc-wrapper
library simplifies the use of JWT and automates the management of signature keys.
Install
pip install joserfc-wrapper
Reason
The main purpose of this wrapper is to simplify the management of signature keys for generating JWT tokens using the joserfc library and adhering to RFC standards. It offers two options for managing signature keys: securely storing generated keys in HashiCorp Vault (default) or storing them on the filesystem (optional). Additionally, it facilitates the use of JWT tokens in projects.
Need a custom solution for storing keys? We've got you covered.
If necessary, a custom object can be created to manage signing keys, including storing them in a database. However, this custom class must be a subclass of the parent AbstractKeyStorage abstract class to implement the necessary methods.
Configuration
# file storage
storage = StorageFile(
cert_dir="/tmp"
)
# HashiCorp Vault storage
storage = StorageVault(
url="<vault url>,
token="<token>",
mount="<secure mount>"
)
Header and claims in this wrapper
# decoded header (all created automatically)
{
'typ': 'JWT', # created automatically
'alg': 'ES256', # created automatically
'kid': 'cdfef1a0e8414b25a593e50c47e59dcb' # Key ID - created automatically
}
# decoded claims
{
'iss': 'https://example.com', # required
'aud': 'auditor', # required
'uid': 123, # required
'iat': 1705418960 # created automatically
}
Create new signature keys
from hvac.exceptions import InvalidPath
from joserfc_wrapper.exceptions import ObjectTypeError
from joserfc_wrapper import WrapJWK, StorageVault, StorageFile
""" With file storage """
file = StorageFile(cert_dir="/tmp")
myjwk = WrapJWK(storage=file)
""" With Vault storage """
vault = StorageVault(
url="<vault url>,
token="<token>",
mount="<secure mount>"
)
myjwk = WrapJWK(storage=vault)
# generate a new keys
myjwk.generate_keys()
# save new keys to a storage
myjwk.save_keys()
Examples
""" Required claims """
claims = {
"iss": "https://example.com",
"aud": "auditor",
"uid": 123,
}
try:
""" Create token """
myjwt = WrapJWT(wrapjwk=myjwk)
# only the last generated key is always used to create a new token
token = myjwt.create(claims=claims)
print(f"Token: {token[:20]}..., Length: {len(token)}bytes")
""" Create token with encrypted data """
myjwe = WrapJWE(wrapjwk=myjwk)
secret_data = "very secret text"
secret_data_bytes = b"very secrets bytes"
claims["sec"] = myjwe.encrypt(data=secret_data)
claims["sec_bytes"] = myjwe.encrypt(data=secret_data_bytes)
print(f'[sec]: {claims["sec"]}')
token_with_sec = myjwt.create(claims=claims)
print(f"Token: {token_with_sec[:20]}..., Length: {len(token_with_sec)}bytes")
""" Validate token """
try:
myjwt = WrapJWT(wrapjwk=myjwk)
# return extracted token object Token
valid_token = myjwt.decode(token=token)
print(valid_token.header)
print(valid_token.claims)
except BadSignatureError as e:
print(f"{e}")
# check if claims in token are valid
invalid_claims = {
"aud": "any",
"iss": "any"
}
try:
myjwt.validate(token=valid_token, claims=invalid_claims)
except InvalidClaimError as e:
# invalid_claim: Invalid claim: "iss"
print(e)
""" Validate invalid token (signature key not exist) """
try:
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjM5MTkxZDUyM2Q4MTQ3NTZiYTgxMWNmZWFjODY0YjNjIn0.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiYXVkIjoiYXVkaXRvciIsInVpZCI6MTIzLCJpYXQiOjE3MDUyNzc3OTR9.r7uflHLnSIMxhma0eU_A7hRupL3ZDUjXGgSMprOmWdDzMh1TRDFxW8CPzOhnVDZLfPeyjjt4KYn6jPT2W2E9jg"
myjwt = WrapJWT(wrapjwk=myjwk)
# here is raise InvalidPath because kid not in a storage
valid_token = myjwt.decode(token=token)
except InvalidPath as e:
print(f"{e}")
""" Validate fake token """
try:
token = "faketoken"
myjwt = WrapJWT(wrapjwk=myjwk)
# here is raise InvalidPath because kid not in a storage
valid_token = myjwt.decode(token=token)
except ValueError as e:
print(f"{e}")
""" Validate token and decrypt secret data """
myjwt = WrapJWT(wrapjwk=myjwk)
myjwe = WrapJWE(wrapjwk=myjwk)
valid_token = myjwt.decode(token=token_with_sec)
# decrypt return b'' in all situations
secret_data = myjwe.decrypt(valid_token.claims["sec"], myjwt.get_kid())
secret_data_bytes = myjwe.decrypt(valid_token.claims["sec_bytes"], myjwt.get_kid())
print(f"[sec]: {secret_data}")
print(f"[sec_bytes]: {secret_data_bytes}")
except InvalidPath as e:
# create JWK first
print(f"Invalid path because key not exist in the storage.")
print(f"{e}")
A bit of magic
By default, it is possible to sign an unlimited number of tokens with a single key. However, this approach may not always be appropriate. Instead, a more efficient solution can be implemented by setting the payload as the maximum number of tokens that can be signed with the same key, thus saving storage space. It is important to keep in mind that the keys are stored, so a suitable compromise must be found when setting the payload to avoid storage overflow.
""" payload """
token = myjwt.create(claims=claims, payload=10)
print(f"Token: {token[:30]}..., Length: {len(token)}bytes")
CLI
Exceptions
For debugging is there are a few exceptions which can be found here:
Contributions to the development of this library are welcome, ideally in the form of a pull request.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for joserfc_wrapper-0.1.9.dev1.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | 629e8cadaad3b26335132e7e0f6f56cf187483db98bb9e711372562e03a278aa |
|
MD5 | 541edbbdaff6883c8ec20a22934384b3 |
|
BLAKE2b-256 | 5d8a5c076b717e7c8066a65f56ebe657aa9104597c4e4d48a0ca031c67a91396 |
Hashes for joserfc_wrapper-0.1.9.dev1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | dd1cd074fa89714ffac0f8ad9e060c1705dd3f5976bcc0b5b17617b2f1ca2651 |
|
MD5 | 6aa4c01aca6ab68351b52e0f69a8087f |
|
BLAKE2b-256 | 565b074c3b51dae547ba9729d614ef19e0bbe2f063bef2c9dc7b90b2cf5d6384 |