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, 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 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 (user, request, claims) -> 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.authenticated_userid: Get the authenticated user ID
  • request.jwt_claims: Access decoded JWT claims
  • 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 validation_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


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.0.tar.gz (39.0 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.0-py3-none-any.whl (7.3 kB view details)

Uploaded Python 3

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

Hashes for pyramid_jwt2-1.0.0.tar.gz
Algorithm Hash digest
SHA256 0e7597d3853d0d2958a58cd64b9dafb39adf6fa3a45e4f2c65a75184f62b227d
MD5 12a98d43abfa20eaa9c7884f802eeb63
BLAKE2b-256 7f51909601c26bf62b2e82e14bb11a884637bb6dfe98acad719237bd4abe9452

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyramid_jwt2-1.0.0.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.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

Hashes for pyramid_jwt2-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 868a989de5182d00b28a5bb3bf9d37170356325a4ffd032f34a15df76e82cadb
MD5 00330a93b0d18d63bb6f33697dcfc2e8
BLAKE2b-256 d6a33215c8a8c1d6b57e3f262c8326aece4c083b77e122556dcf148e8aabd358

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyramid_jwt2-1.0.0-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