Skip to main content

Sign tokens with blake2b, then verify them.

Project description

bootleg-jwt

bootleg-jwt aims to mimic JSON Web Tokens in a simple, pydantic way.


New for v0.3.0

Version 0.3.0 extends v0.2.x with backwards compatible addition of the _salt and _person arguments. When these arguments are passed alongside a token, the token's hash is generated using them. Note: the _salt and _person arguments may not be longer than 16 bytes. This is a limitation of the blake2b hashing algorithm. If a string is supplied, it will be encoded to utf-8 and truncated automatically. Otherwise, any bytestring longer than 16 bytes will fail validation. This is by design.

_salt and _person allow the token to be namespaced and salted. This provides an extra layer of validation, allowing implementations of BootlegJWT to perform more checks on tokens, other than what is baked in. This would allow, for example, invalidating all tokens with a particular salt or namespace.


Table of Contents

Generate a token

from bootleg_jwt import BootlegJWT, Payload, header, body
from pydantic import BaseModel
from os import environ              # An environment variable is required.


SECRET = "some-secret-key"


DURATION = 60 * 60                  # Token expires after this many seconds


TYPE = "Testing Token"              # An arbitrary name


environ['SECRET'] = SECRET          # This module depends upon an environment
                                    # variable `SECRET`. You may also set this
                                    # secret in a `.env` file in your project's root,
                                    # or by using `export SECRET="secret"`



## These two pydantic models are simple examples. They may have arbitrary names and data. They must only map to Token.body.user and Token.body.data
class UserData(BaseModel):
    id: int
    username: str


class BodyData(BaseModel):
    info: str
    value: bool


payload_user = UserData(id=69,username="nice")
payload_body = BodyData(info="Some Information", value=True)


payload = Payload(
    header=header(duration=DURATION,type=TYPE),
    body=body(user=payload_user,data=payload_body)
)


generate = BootlegJWT(payload=payload)
token = generate.TOKEN
encoded = generate.ENCODED
json = generate.JSON
validate = generate.VALID
divider = "\n------------------------------\n"



print(token,divider,encoded,divider,json,divider,validate)
Output (click to expand):
header=Header(type='Testing Token', duration=Duration(unit=Unit(type='time', name='seconds', shorthand='s'), value=3600), created=Timestamp(unit=Unit(type='time', name='seconds since epoch', shorthand='s+epoch'), value=1677382369), expires=Timestamp(unit=Unit(type='time', name='seconds since epoch', shorthand='s+epoch'), value=1677385969)) body=Body(user=UserData(id=69, username='nice'), data=BodyData(info='Some Information', value=True)) signature=Hash(value=b'e0b99c4eca2811bd9d164185219a283a4fddc2129ec0d2b3e5ba7b22596e4c7b8ac6d7b7c81812b50ace0a5b6d0be3ce5f977f753069d951bf15d13f179014df', algorithm='blake2b', keyed=True, salt=b'', person=b'')
------------------------------
 b'eyJoZWFkZXIiOiB7InR5cGUiOiAiVGVzdGluZyBUb2tlbiIsICJkdXJhdGlvbiI6IHsidW5pdCI6IHsidHlwZSI6ICJ0aW1lIiwgIm5hbWUiOiAic2Vjb25kcyIsICJzaG9ydGhhbmQiOiAicyJ9LCAidmFsdWUiOiAzNjAwfSwgImNyZWF0ZWQiOiB7InVuaXQiOiB7InR5cGUiOiAidGltZSIsICJuYW1lIjogInNlY29uZHMgc2luY2UgZXBvY2giLCAic2hvcnRoYW5kIjogInMrZXBvY2gifSwgInZhbHVlIjogMTY3NzM4MjM2OX0sICJleHBpcmVzIjogeyJ1bml0IjogeyJ0eXBlIjogInRpbWUiLCAibmFtZSI6ICJzZWNvbmRzIHNpbmNlIGVwb2NoIiwgInNob3J0aGFuZCI6ICJzK2Vwb2NoIn0sICJ2YWx1ZSI6IDE2NzczODU5Njl9fSwgImJvZHkiOiB7InVzZXIiOiB7ImlkIjogNjksICJ1c2VybmFtZSI6ICJuaWNlIn0sICJkYXRhIjogeyJpbmZvIjogIlNvbWUgSW5mb3JtYXRpb24iLCAidmFsdWUiOiB0cnVlfX0sICJzaWduYXR1cmUiOiB7InZhbHVlIjogImUwYjk5YzRlY2EyODExYmQ5ZDE2NDE4NTIxOWEyODNhNGZkZGMyMTI5ZWMwZDJiM2U1YmE3YjIyNTk2ZTRjN2I4YWM2ZDdiN2M4MTgxMmI1MGFjZTBhNWI2ZDBiZTNjZTVmOTc3Zjc1MzA2OWQ5NTFiZjE1ZDEzZjE3OTAxNGRmIiwgImFsZ29yaXRobSI6ICJibGFrZTJiIiwgImtleWVkIjogdHJ1ZSwgInNhbHQiOiAiIiwgInBlcnNvbiI6ICIifX0='
------------------------------
 {
    "header": {
        "type": "Testing Token",
        "duration": {
            "unit": {
                "type": "time",
                "name": "seconds",
                "shorthand": "s"
            },
            "value": 3600
        },
        "created": {
            "unit": {
                "type": "time",
                "name": "seconds since epoch",
                "shorthand": "s+epoch"
            },
            "value": 1677382369
        },
        "expires": {
            "unit": {
                "type": "time",
                "name": "seconds since epoch",
                "shorthand": "s+epoch"
            },
            "value": 1677385969
        }
    },
    "body": {
        "user": {
            "id": 69,
            "username": "nice"
        },
        "data": {
            "info": "Some Information",
            "value": true
        }
    },
    "signature": {
        "value": "e0b99c4eca2811bd9d164185219a283a4fddc2129ec0d2b3e5ba7b22596e4c7b8ac6d7b7c81812b50ace0a5b6d0be3ce5f977f753069d951bf15d13f179014df",
        "algorithm": "blake2b",
        "keyed": true,
        "salt": "",
        "person": ""
    }
}
------------------------------
 True

Validate a token

from bootleg_jwt import BootlegJWT, Payload, header, body
from pydantic import BaseModel
from os import environ              # An environment variable is required.


SECRET = "some-secret-key"


DURATION = 60 * 60                  # Token expires after this many seconds


TYPE = "Testing Token"              # An arbitrary name


environ['SECRET'] = SECRET          # This module depends upon an environment
                                    # variable `SECRET`. You may also set this
                                    # secret in a `.env` file in your project's root,
                                    # or by using `export SECRET="secret"`



## These two pydantic models are simple examples. They may have arbitrary names and data. They must only map to Token.body.user and Token.body.data
class UserData(BaseModel):
    id: int
    username: str


class BodyData(BaseModel):
    info: str
    value: bool


payload_user = UserData(id=69,username="nice")
payload_body = BodyData(info="Some Information", value=True)


payload = Payload(
    header=header(duration=DURATION,type=TYPE),
    body=body(user=payload_user,data=payload_body)
)


divider = "\n------------------------------\n"


def generate(payload):
    generate = BootlegJWT(payload=payload)
    decoded = generate.DECODED
    encoded = generate.ENCODED
    json = generate.JSON
    validate = generate.VALID
    print(decoded,divider,encoded,divider,json,divider,validate)
    return encoded


def validate_token(token):
    validate_token = BootlegJWT(token=token)
    v_decoded = validate_token.DECODED
    v_json = validate_token.JSON
    v_valid = validate_token.VALID
    print(v_decoded,divider,v_json,divider,v_valid)


validate_token(generate(payload))
Output (click to expand):
header=Header(type='Testing Token', duration=Duration(unit=Unit(type='time', name='seconds', shorthand='s'), value=3600), created=Timestamp(unit=Unit(type='time', name='seconds since epoch', shorthand='s+epoch'), value=1677383225), expires=Timestamp(unit=Unit(type='time', name='seconds since epoch', shorthand='s+epoch'), value=1677386825)) body=Body(user=UserData(id=69, username='nice'), data=BodyData(info='Some Information', value=True)) signature=Hash(value=b'9a6a3fc5c866442ee886c1d20f44fe49da29be4e56fd6f40a1c3e23f672d801c0d787f9f239265477da1339fffc41754f16a0899f5955aa0ed7602693919071d', algorithm='blake2b', keyed=True, salt=b'', person=b'')
------------------------------
 b'eyJoZWFkZXIiOiB7InR5cGUiOiAiVGVzdGluZyBUb2tlbiIsICJkdXJhdGlvbiI6IHsidW5pdCI6IHsidHlwZSI6ICJ0aW1lIiwgIm5hbWUiOiAic2Vjb25kcyIsICJzaG9ydGhhbmQiOiAicyJ9LCAidmFsdWUiOiAzNjAwfSwgImNyZWF0ZWQiOiB7InVuaXQiOiB7InR5cGUiOiAidGltZSIsICJuYW1lIjogInNlY29uZHMgc2luY2UgZXBvY2giLCAic2hvcnRoYW5kIjogInMrZXBvY2gifSwgInZhbHVlIjogMTY3NzM4MzIyNX0sICJleHBpcmVzIjogeyJ1bml0IjogeyJ0eXBlIjogInRpbWUiLCAibmFtZSI6ICJzZWNvbmRzIHNpbmNlIGVwb2NoIiwgInNob3J0aGFuZCI6ICJzK2Vwb2NoIn0sICJ2YWx1ZSI6IDE2NzczODY4MjV9fSwgImJvZHkiOiB7InVzZXIiOiB7ImlkIjogNjksICJ1c2VybmFtZSI6ICJuaWNlIn0sICJkYXRhIjogeyJpbmZvIjogIlNvbWUgSW5mb3JtYXRpb24iLCAidmFsdWUiOiB0cnVlfX0sICJzaWduYXR1cmUiOiB7InZhbHVlIjogIjlhNmEzZmM1Yzg2NjQ0MmVlODg2YzFkMjBmNDRmZTQ5ZGEyOWJlNGU1NmZkNmY0MGExYzNlMjNmNjcyZDgwMWMwZDc4N2Y5ZjIzOTI2NTQ3N2RhMTMzOWZmZmM0MTc1NGYxNmEwODk5ZjU5NTVhYTBlZDc2MDI2OTM5MTkwNzFkIiwgImFsZ29yaXRobSI6ICJibGFrZTJiIiwgImtleWVkIjogdHJ1ZSwgInNhbHQiOiAiIiwgInBlcnNvbiI6ICIifX0='
------------------------------
 {
    "header": {
        "type": "Testing Token",
        "duration": {
            "unit": {
                "type": "time",
                "name": "seconds",
                "shorthand": "s"
            },
            "value": 3600
        },
        "created": {
            "unit": {
                "type": "time",
                "name": "seconds since epoch",
                "shorthand": "s+epoch"
            },
            "value": 1677383225
        },
        "expires": {
            "unit": {
                "type": "time",
                "name": "seconds since epoch",
                "shorthand": "s+epoch"
            },
            "value": 1677386825
        }
    },
    "body": {
        "user": {
            "id": 69,
            "username": "nice"
        },
        "data": {
            "info": "Some Information",
            "value": true
        }
    },
    "signature": {
        "value": "9a6a3fc5c866442ee886c1d20f44fe49da29be4e56fd6f40a1c3e23f672d801c0d787f9f239265477da1339fffc41754f16a0899f5955aa0ed7602693919071d",
        "algorithm": "blake2b",
        "keyed": true,
        "salt": "",
        "person": ""
    }
}
------------------------------
 True
header=Header(type='Testing Token', duration=Duration(unit=Unit(type='time', name='seconds', shorthand='s'), value=3600), created=Timestamp(unit=Unit(type='time', name='seconds since epoch', shorthand='s+epoch'), value=1677383225), expires=Timestamp(unit=Unit(type='time', name='seconds since epoch', shorthand='s+epoch'), value=1677386825)) body=Body(user={'id': 69, 'username': 'nice'}, data={'info': 'Some Information', 'value': True}) signature=Hash(value=b'9a6a3fc5c866442ee886c1d20f44fe49da29be4e56fd6f40a1c3e23f672d801c0d787f9f239265477da1339fffc41754f16a0899f5955aa0ed7602693919071d', algorithm='blake2b', keyed=True, salt=b'', person=b'')
------------------------------
 {
    "header": {
        "type": "Testing Token",
        "duration": {
            "unit": {
                "type": "time",
                "name": "seconds",
                "shorthand": "s"
            },
            "value": 3600
        },
        "created": {
            "unit": {
                "type": "time",
                "name": "seconds since epoch",
                "shorthand": "s+epoch"
            },
            "value": 1677383225
        },
        "expires": {
            "unit": {
                "type": "time",
                "name": "seconds since epoch",
                "shorthand": "s+epoch"
            },
            "value": 1677386825
        }
    },
    "body": {
        "user": {
            "id": 69,
            "username": "nice"
        },
        "data": {
            "info": "Some Information",
            "value": true
        }
    },
    "signature": {
        "value": "9a6a3fc5c866442ee886c1d20f44fe49da29be4e56fd6f40a1c3e23f672d801c0d787f9f239265477da1339fffc41754f16a0899f5955aa0ed7602693919071d",
        "algorithm": "blake2b",
        "keyed": true,
        "salt": "",
        "person": ""
    }
}
------------------------------
 True

Renew a token

Using BootlegJWT.renew(token), you may pass a token to BootlegJWT in bytestring form. This function will return an instance of BootlegJWT with a new token and expiration date, retaining all of the input token's properties.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bootleg-jwt-0.3.0.tar.gz (9.3 kB view hashes)

Uploaded Source

Built Distribution

bootleg_jwt-0.3.0-py3-none-any.whl (9.9 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page