Skip to main content

JWT authentication security policy for Pyramid 2.0+

Project description

pyramid_jwt2

PyPI version Python versions CI License: MIT

JWT authentication security policy for Pyramid 2.0+.

This package provides a modern, type-safe JWT authentication implementation for Pyramid web framework, an alternative to the existing pyramid_jwt for users using Pyramid 2.0's unified security policy.

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.request = request

    @property
    def __acl__(self):
        if self.request.identity:
            # 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) -> 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 JWTs
  • user_loader (callable, required): Function (userid, request) -> user object | None
    • Loads user from database once and caches it
    • Return None if 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 True if token is valid, False if invalid
    • Receives user object from user_loader
    • Use for logout timestamps, banned users, etc.
  • additional_principals (callable, optional): Function (request, user) -> set[str]
    • Returns additional principals to add (e.g., {"role:admin"})
    • Enables clean role-based ACLs

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 token
  • request.jwt_claims_from_token(token): Decode and validate an arbitrary token, returning its claims. Raises jwt.InvalidTokenError if the token is invalid or expired
  • request.authenticated_userid: Get the authenticated user ID
  • request.jwt_claims: Access decoded JWT claims. Only populated after request.identity has been resolved and the token decoded successfully
  • request.identity: Get identity dict with:
    • userid: User ID from token
    • claims: Decoded JWT claims
    • user: 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() raises NotImplementedError - use create_jwt_token() instead
  • forget() raises NotImplementedError - handle logout via custom_token_validation

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


Download files

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

Source Distribution

pyramid_jwt2-1.0.1.tar.gz (40.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyramid_jwt2-1.0.1-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file pyramid_jwt2-1.0.1.tar.gz.

File metadata

  • Download URL: pyramid_jwt2-1.0.1.tar.gz
  • Upload date:
  • Size: 40.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyramid_jwt2-1.0.1.tar.gz
Algorithm Hash digest
SHA256 057cbf9643b66461f5f98cdcc729c748c389856aac5ea4d1caa1429c4a88917c
MD5 5a9bcf47e54d5c1fdf712518e1f4fbc3
BLAKE2b-256 c594e56888c88592979ef1b1589b88d08fef996bd2ced170f202afdb2bd1d156

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyramid_jwt2-1.0.1.tar.gz:

Publisher: publish.yml on teamniteo/pyramid_jwt2

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyramid_jwt2-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: pyramid_jwt2-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 7.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pyramid_jwt2-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 307d26e1b9aca27879ead9d8cef7c90728c42cb02f1ab4797972224b236a466c
MD5 ea645accadd0d5ca426a43041725776e
BLAKE2b-256 7e304811511c2fbd65cdc448201990a66693c2195841b0fab8f7e7af976da440

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyramid_jwt2-1.0.1-py3-none-any.whl:

Publisher: publish.yml on teamniteo/pyramid_jwt2

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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