A JSON Web Token library for the Molten web framework
Project description
molten-jwt
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.Settings
component, 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.
History
0.3.0 Change / Added / Fixed
JWTAuthMiddleware
now validates JWT claims using therequired_claims
decorator and the standardAuthlib
validation mechanism.Authlib
claim options can be passed to the component using theJWT_CLAIMS_OPTIONS
setting.JWTAuthMiddleware
now raises an HTTP 403 error ifrequired_claims
check fails.JWT_ALGORITHM
is now a required setting when usingJWTComponent
. HS256 is no longer a default and will raise aConfigurationError
if None.JWT_PRIVATE_KEY_FILE
andJWT_PUBLIC_KEY_FILE
are now options in settings and are required for RS*, ES*, and PS* algorithms.
0.2.1 Change / Fixed
- Fixed README.md code examples
- Pinned Authlib version due to API changes in Authlib 0.11
0.2.0 Change / Added / Fixed
JWTUser
is now known asJWTIdentity
JWTIdentity
now has dynamic attribute access to its token claims via standard dot notation- Authentication code and components have been relocated to
molten_jwt.auth
JWT
is now a simple wrapper aroundauthlib.jwt
with no dependencies on themolten.Settings
.JWTComponent
will return a single JWT instance configured from the settings passed in themolten.Settings
JWTIdentityComponent
now has a setting to extract a JWTdentity from a json web token passed in a named cookie.JWTAuthMiddleware
now has new settings to control authentication checking, including a whitelist of handlers.
0.1.1 Added / Fixed
- Updated documentation before push to Pypi
- Fixed bumpversion replacement string
0.1.0 Change
Switched from using PyJWT to Authlib for JWT support
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
Built Distribution
File details
Details for the file molten_jwt-0.3.0.tar.gz
.
File metadata
- Download URL: molten_jwt-0.3.0.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/40.2.0 requests-toolbelt/0.9.1 tqdm/4.38.0 CPython/3.6.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 12664010b3a771012d2900afbb1f8d4b52a7e795fb1e3a29f39adfb198fefb2e |
|
MD5 | 294bedf5995f57f4c84f542dc59ef84d |
|
BLAKE2b-256 | 479f90ad1171f8cfb88f6ef3cb4203370df69973d6ae5547734f235428ad0751 |
File details
Details for the file molten_jwt-0.3.0-py36-none-any.whl
.
File metadata
- Download URL: molten_jwt-0.3.0-py36-none-any.whl
- Upload date:
- Size: 11.5 kB
- Tags: Python 3.6
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/40.2.0 requests-toolbelt/0.9.1 tqdm/4.38.0 CPython/3.6.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4036c39acaca3ac6515a27346b662fba1be4ec761f02c2b76cd1d075dee7a15f |
|
MD5 | 8ea5a6c768ea0a5a2602123ba9100d4a |
|
BLAKE2b-256 | cf20cf744eeef435c3d8fa4836819cfab52172dc6e29d7d14db5d0f47c0e60b5 |