JWT authentication security policy for Pyramid 2.0+
Project description
pyramid_jwt2
JWT authentication security policy for Pyramid 2.0+.
This package provides a modern, type-safe JWT authentication implementation for Pyramid web framework, replacing the deprecated pyramid_jwt package with native Pyramid 2.0 security policies.
Features
- ✅ Pyramid 2.0+ unified security policy (no deprecated auth/authz split)
- ✅ JWT token creation and validation using PyJWT
- ✅ ACL-based permissions support
- ✅ Custom validation callbacks
- ✅ Bearer token authentication
- ✅ Fully typed (PEP 561 compatible)
Installation
pip install pyramid-jwt2
Usage
Basic Configuration
from pyramid.config import Configurator
from pyramid_jwt2 import set_jwt_authentication_policy
def load_user(userid, request):
"""Load user from database."""
return request.db.query(User).filter(User.id == userid).first()
def main(global_config, **settings):
config = Configurator(settings=settings)
# Configure JWT authentication
set_jwt_authentication_policy(
config,
secret=settings['jwt.secret'],
user_loader=load_user, # Required: load user from DB
auth_type='Bearer', # Authorization: Bearer <token>
)
return config.make_wsgi_app()
Creating Tokens
from pyramid.view import view_config
@view_config(route_name='login', request_method='POST', renderer='json')
def login(request):
# Validate credentials...
user_id = "12345"
# Create JWT token
token = request.create_jwt_token(
user_id,
expiration=timedelta(hours=48)
)
return {'token': token}
Protected Views
from pyramid.view import view_config
@view_config(
route_name='protected',
request_method='GET',
renderer='json',
permission='authenticated'
)
def protected_view(request):
user_id = request.authenticated_userid
return {'user_id': user_id}
Custom Token Validation
Implement custom token validation (e.g., invalidate token if it was issued before the last user logout, invalidate token if it was issued before password changed, etc.):
def load_user(userid: str, request: Request) -> User | None:
"""Load user from database."""
return request.db.query(User).filter(User.id == userid).first()
def validate_token(user: User, request: Request) -> bool:
"""
Validate token - receives user object from user_loader.
Return True if token is valid, False if invalid.
"""
if user is None:
return False # User not found
# Check if token was issued before user logged out
if user.logged_out:
issued_at = datetime.fromtimestamp(request.jwt_claims['iat'])
if issued_at < user.logged_out:
return False # Token invalidated by logout
return True # Valid token
# Configure with user_loader and validation
set_jwt_authentication_policy(
config,
secret=settings['jwt.secret'],
user_loader=load_user,
custom_token_validation=validate_token,
)
ACL Permissions
Use with Pyramid's ACL system:
from pyramid.authorization import Allow, Authenticated
class RootFactory:
def __init__(self, request):
self.user = request.user
@property
def __acl__(self):
if self.user:
# Grant permissions using Authenticated principal
return [
(Allow, Authenticated, 'view'),
(Allow, Authenticated, 'edit'),
]
return []
config.set_root_factory(RootFactory)
For user-specific permissions, the userid is also available as a principal:
# Allow document owner to delete
class DocumentContext:
def __init__(self, document_id, request):
self.document = get_document(document_id)
@property
def __acl__(self):
return [
(Allow, Authenticated, 'view'),
(Allow, self.document.owner_id, 'delete'), # Only owner can delete
]
Additional Principals (Role-Based Access)
Add custom principals like roles for cleaner ACLs:
def load_user(userid, request):
return request.db.query(User).filter(User.id == userid).first()
def add_role_principals(request, user: User, claims: dict) -> set[str]:
"""Add role-based principals."""
principals = set()
if user and user.role:
principals.add(f"role:{user.role}") # e.g., "role:admin"
return principals
# Configure with principals callback
set_jwt_authentication_policy(
config,
secret=settings['jwt.secret'],
user_loader=load_user,
additional_principals=add_role_principals,
)
# Now use clean role-based ACLs
class RootFactory:
@property
def __acl__(self):
return [
(Allow, 'role:admin', ALL_PERMISSIONS),
(Allow, 'role:editor', 'edit'),
(Allow, Authenticated, 'view'),
]
Configuration Options
set_jwt_authentication_policy(config, secret, user_loader, **options)
secret(str, required): Secret key for signing/verifying JWTsuser_loader(callable, required): Function(userid, request) -> user object | None- Loads user from database once and caches it
- Return
Noneif user doesn't exist (auth fails)
algorithm(str, optional): JWT algorithm (default:"HS512")auth_type(str, optional): Authorization header type (default:"Bearer")custom_token_validation(callable, optional): Function(user, request) -> bool- Return
Trueif token is valid,Falseif invalid - Receives user object from user_loader
- Use for logout timestamps, banned users, etc.
- Return
additional_principals(callable, optional): Function(user, request, claims) -> set[str]- Returns additional principals to add (e.g.,
{"role:admin"}) - Enables clean role-based ACLs
- Returns additional principals to add (e.g.,
API Reference
Request Methods
After configuration, these methods are available on the request object:
request.create_jwt_token(userid, expiration=None, **claims): Create a JWT tokenrequest.authenticated_userid: Get the authenticated user IDrequest.jwt_claims: Access decoded JWT claimsrequest.identity: Get identity dict with:userid: User ID from tokenclaims: Decoded JWT claimsuser: User object from user_loader (cached, no redundant DB queries)
Stateless Authentication
JWT is stateless, meaning authentication doesn't use cookies or sessions. Therefore:
remember()raisesNotImplementedError- usecreate_jwt_token()insteadforget()raisesNotImplementedError- handle logout viavalidation_callback
Requirements
- Python 3.11+
- Pyramid 2.0+
- PyJWT 2.0+
Development
This project uses uv and ruff.
uv sync --dev # install dependencies into .venv/
make check # ruff lint + format check
make unit # run tests with coverage
make tests # check + unit (run before every commit)
make build # build sdist + wheel into dist/
See RELEASE.md for the release process.
License
MIT
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyramid_jwt2-1.0.0.tar.gz.
File metadata
- Download URL: pyramid_jwt2-1.0.0.tar.gz
- Upload date:
- Size: 39.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e7597d3853d0d2958a58cd64b9dafb39adf6fa3a45e4f2c65a75184f62b227d
|
|
| MD5 |
12a98d43abfa20eaa9c7884f802eeb63
|
|
| BLAKE2b-256 |
7f51909601c26bf62b2e82e14bb11a884637bb6dfe98acad719237bd4abe9452
|
Provenance
The following attestation bundles were made for pyramid_jwt2-1.0.0.tar.gz:
Publisher:
publish.yml on teamniteo/pyramid_jwt2
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyramid_jwt2-1.0.0.tar.gz -
Subject digest:
0e7597d3853d0d2958a58cd64b9dafb39adf6fa3a45e4f2c65a75184f62b227d - Sigstore transparency entry: 1320794872
- Sigstore integration time:
-
Permalink:
teamniteo/pyramid_jwt2@964ae3bf638c14c5f8adcd6ebdd25883cdeb25a9 -
Branch / Tag:
refs/tags/1.0.0 - Owner: https://github.com/teamniteo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@964ae3bf638c14c5f8adcd6ebdd25883cdeb25a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyramid_jwt2-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pyramid_jwt2-1.0.0-py3-none-any.whl
- Upload date:
- Size: 7.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
868a989de5182d00b28a5bb3bf9d37170356325a4ffd032f34a15df76e82cadb
|
|
| MD5 |
00330a93b0d18d63bb6f33697dcfc2e8
|
|
| BLAKE2b-256 |
d6a33215c8a8c1d6b57e3f262c8326aece4c083b77e122556dcf148e8aabd358
|
Provenance
The following attestation bundles were made for pyramid_jwt2-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on teamniteo/pyramid_jwt2
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyramid_jwt2-1.0.0-py3-none-any.whl -
Subject digest:
868a989de5182d00b28a5bb3bf9d37170356325a4ffd032f34a15df76e82cadb - Sigstore transparency entry: 1320794954
- Sigstore integration time:
-
Permalink:
teamniteo/pyramid_jwt2@964ae3bf638c14c5f8adcd6ebdd25883cdeb25a9 -
Branch / Tag:
refs/tags/1.0.0 - Owner: https://github.com/teamniteo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@964ae3bf638c14c5f8adcd6ebdd25883cdeb25a9 -
Trigger Event:
push
-
Statement type: