Skip to content

A JSON Web Token component and middleware for Molten

License

Notifications You must be signed in to change notification settings

androiddrew/molten-jwt

Repository files navigation

molten-jwt

PyPI PyPI Build Status codecov

A JSON Web Token(JWT) library built on top of Authlib for use in the Molten web framework.

Usage

JWT

The JWT class provides the core methods to encode and decode JSON Web Tokens in your application or middleware. All tokens produced are signed with a key and algorithm according to the JSON Web Signature(JWS) specification.

Note: Signing a token does not mean that the token contents are encrypted. This signature is used to prevent tampering. Take care not to expose private information in unencrypted tokens. Also please always use transport level security (TLS)

from molten_jwt import JWT

jwt = JWT(key='asecretkeyforsigning', alg="HS256")

token = jwt.encode({'sub': 'superman'})

decoded = jwt.decode(token)

JWT with dependency injection

Register the JWTComponent with your Molten application and provide a JWT_SECRET_KEY in the molten Settings. The SettingsComponent is utilized to provide the configuration for your JWT injectable instance. Now simply annotate your a handler param with the JWT type and use it to encode your JSON Web Token.

from typing import Dict
from molten import (
    App,
    Route,
    Settings,
    SettingsComponent,
    schema,
    field,
    HTTP_403,
    HTTP_500,
)
from molten.errors import HTTPError

from molten_jwt import JWT, JWTComponent

settings = Settings({"JWT_SECRET_KEY": "donotcommittoversioncontrol"})


@schema
class UserData:
    email: str
    password: str = field(request_only=True)


def db_login(data: UserData):
    # DB magic happens here. This is just to have a working example for copy pasta
    setattr(data, "id", 1)
    return data


def login(data: UserData, jwt: JWT) -> Dict:
    # Perform the authentication task with your data layer
    user = db_login(data)
    if not user:
        raise HTTPError(HTTP_403, "Incorrect username or password")

    payload = {"sub": user.id, "name": user.email, "other_data": "12345"}
    try:
        token = jwt.encode(payload)
    except Exception:
        raise HTTPError(HTTP_500, "Internal error encountered")

    return {"token": token}


components = [SettingsComponent(settings), JWTComponent()]

routes = [Route("/login", login, method="POST")]

app = App(routes=routes, components=components)

JWTIdentity

A JWTIdentity component can be added to your application to provide a user representation from a decoded access token. By default this library assumes your access token is sent in the Authorization header of the request. Alternatively, you can provide a cookie name using JWT_AUTH_COOKIE within your settings, however current functionality does not support both methods. Add the JWTIdentityComponent to your app's component list then inject the JWTIdentity into your handler. In the event that the Authorization header / cookie is not found or if an error occurs in the decoding of the token the JWTIdentityComponent will return None.

...

from molten_jwt import JWT, JWTIdentity, JWTComponent, JWTIdentityComponent

...


def protected_endpoint(jwt_user: JWTIdentity) -> Dict:
    if jwt_user is None:
        raise HTTPError(HTTP_403, "Forbidden")

    return {"user_id": jwt_user.id, "name": jwt_user.user_name, "token": jwt_user.token}


components = [SettingsComponent(settings), JWTComponent(), JWTIdentityComponent()]

routes = [
    Route("/login", login, method="POST"),
    Route("/safe", protected_endpoint, method="GET"),
]

app = App(routes=routes, components=components)

JWTAuthMiddleware

The JWTAuthMiddleware can be added to your application to globally validate that a JSON Web Token was passed within the Authorization header or a named cookie of the request. This middleware depends on the availability of a molten.Settingscomponent, a molten_jwt.JWT component, and a molten_jwt.JWTIdentity component.

Use the molten_jwt.decorators.allow_anonymous decorator to allow for non-authenticated access to endpoints when using this middleware. Alternatively, the JWT_AUTH_WHITELIST setting can be used to provided a list of handler names that should skip authentication checks.

from typing import Dict
from molten import (
    App,
    Route,
    Settings,
    SettingsComponent,
    schema,
    field,
    HTTP_403,
    HTTP_500,
    ResponseRendererMiddleware,
)
from molten.errors import HTTPError

from molten_jwt import JWT, JWTUser, JWTComponent, JWTUserComponent, JWTMiddleware
from molten_jwt.decorators import allow_anonymous

settings = Settings({"JWT_SECRET": "donotcommittoversioncontrol"})


@schema
class UserData:
    email: str
    password: str = field(request_only=True)


def db_login(data: UserData):
    # DB magic happens here this is just to have a working example
    setattr(data, "id", 1)
    return data


@allow_anonymous
def login(data: UserData, jwt: JWT) -> Dict:
    # Perform the authentication task with your data layer
    user = db_login(data)
    if not user:
        raise HTTPError(HTTP_403, "Incorrect username or password")

    payload = {"sub": user.id, "name": user.email, "other_data": "12345"}
    try:
        token = jwt.encode(payload)
    except Exception:
        raise HTTPError(HTTP_500, "Interal error encountered")

    return {"token": token}


def protected_endpoint(jwt_user: JWTUser) -> Dict:
    """Will raise a 401 HTTP status if a JWT is not present or is invalid"""
    return {"user_id": jwt_user.id, "name": jwt_user.user_name, "token": jwt_user.token}


@allow_anonymous
def anonymous_ok(jwt_user: JWTUser) -> Dict:
    if jwt_user is None:
        return {
            "message": "JWT token not presented or is invalid. Accessing resource as anonymous."
        }
    return {"user_id": jwt_user.id, "name": jwt_user.user_name, "token": jwt_user.token}


components = [SettingsComponent(settings), JWTComponent(), JWTUserComponent()]

middleware = [ResponseRendererMiddleware(), JWTAuthMiddleware()]

routes = [
    Route("/login", login, method="POST"),
    Route("/safe", protected_endpoint, method="GET"),
    Route("/anyone", anonymous_ok, method="GET"),
]

app = App(routes=routes, components=components, middleware=middleware)

Setting Options

The following settings can be used to configure the the behavior of Molten JWT. The key values are uppercase and begin with JWT_.

Setting Purpose Type Default
JWT_SECRET_KEY A secret key used to sign tokens. Required for HS256, HS384, or HS512. str None
JWT_PRIVATE_KEY_FILE A path to a private key file. Required for RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, or PS512 file path None
JWT_PRIVATE_KEY_PASSWD A password used to protect the private key. Optional _Whe _ str None
JWT_PUBLIC_KEY_FILE A path to a public key file. Required for RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, or PS512 file path None
JWT_ALGORITHM The algorithm used to sign tokens. Required str None
JWT_CLAIMS_OPTIONS A dictionary of options to be used in validating a JWTClaims instance's content. dict None
JWT_AUTH_PREFIX Used to determine the prefix of the Authorization header. str "bearer"
JWT_AUTH_USER_ID Claim that holds the JWTIdentity's id. str "sub"
JWT_AUTH_USER_NAME Claim that holds the JWTIdentity's name. str "name"
JWT_AUTH_COOKIE Controls the behavior of JWTAuthMiddleware. If set the middleware will look for a cookie of this name containing a JWT authentication token instead of the Authorization header. str None
JWT_AUTH_WHITELIST A list of handler function names used to by pass authentication checks. To be used instead of the allow_anonymous decorator. List[str] None

Attribution

Many thanks to apistar-jwt for providing the inspiration and starting point for this package.

About

A JSON Web Token component and middleware for Molten

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages