---
title: "Add JWT Authentication to Your Pyramid API"
description: "Secure your Pyramid API using JWT authentication with JWKS."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/python/pyramid/jwt-backend"
framework: "Pyramid"
language: "Python"
authStrategy: "JWT with JWKS"
pageType: use-case
---

# Add JWT Authentication to Your Pyramid API

Secure your Pyramid API using JWT authentication with JWKS.

## How Zuplo Handles It

Let Zuplo issue short-lived JWTs signed with a JWKS your Pyramid backend can verify — no long-lived API keys touch your origin.

## Pyramid Backend Code

```python
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
from jose import jwt
from jose.exceptions import JWTError, ExpiredSignatureError
import requests
from cachetools import TTLCache
import json

ISSUER = "https://my-api-a32f34.zuplo.api/__zuplo/issuer"
JWKS_URL = f"{ISSUER}/.well-known/jwks.json"
ALGORITHMS = ["RS256"]

# Cache for JWKS keys
jwks_cache = TTLCache(maxsize=10, ttl=600)

def get_jwks():
    if JWKS_URL not in jwks_cache:
        resp = requests.get(JWKS_URL)
        resp.raise_for_status()
        jwks_cache[JWKS_URL] = resp.json()
    return jwks_cache[JWKS_URL]

def get_key(token):
    unverified_header = jwt.get_unverified_header(token)
    jwks = get_jwks()
    for jwk in jwks['keys']:
        if jwk['kid'] == unverified_header['kid']:
            return jwt.algorithms.RSAAlgorithm.from_jwk(jwk)
    raise JWTError("Unable to find appropriate key")

def jwt_tween_factory(handler, registry):
    def jwt_tween(request):
        # Skip JWT validation for non-protected routes
        if not request.path.startswith('/protected'):
            return handler(request)

        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            return Response(json.dumps({'error': 'No token provided'}),
                          status=401, content_type='application/json')

        token = auth_header[7:]
        try:
            key = get_key(token)
            payload = jwt.decode(token, key, algorithms=ALGORITHMS, issuer=ISSUER)
            request.user = payload
        except ExpiredSignatureError:
            return Response(json.dumps({'error': 'Token has expired'}),
                          status=401, content_type='application/json')
        except JWTError as e:
            return Response(json.dumps({'error': 'Invalid token', 'details': str(e)}),
                          status=401, content_type='application/json')

        return handler(request)
    return jwt_tween

@view_config(route_name='protected', renderer='json')
def protected_view(request):
    return {'message': 'Access granted', 'user': request.user}

if __name__ == '__main__':
    with Configurator() as config:
        config.add_tween('__main__.jwt_tween_factory')
        config.add_route('protected', '/protected')
        config.scan()
        app = config.make_wsgi_app()

    from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 6543, app)
    server.serve_forever()
```

## Example Request

```bash
curl -X GET \
  'https://your-api.zuplo.dev/your-route' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

## Learn More

- [API Key Authentication on Zuplo](https://zuplo.com/docs/policies/api-key-auth-inbound)
- [JWT Authentication on Zuplo](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound)
- [All use cases](https://zuplo.com/use-cases)
