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

# Add JWT Authentication to Your Bottle API

Secure your Bottle API using JWT authentication with JWKS.

## How Zuplo Handles It

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

## Bottle Backend Code

```python
from bottle import Bottle, request, response, abort
from jose import jwt
from jose.exceptions import JWTError, ExpiredSignatureError
import requests
from cachetools import TTLCache
from functools import wraps

app = Bottle()

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 require_jwt(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        if not auth_header or not auth_header.startswith('Bearer '):
            response.status = 401
            return {'error': 'No token provided'}

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

        return f(*args, **kwargs)
    return decorated

@app.route('/protected')
@require_jwt
def protected():
    return {'message': 'Access granted', 'user': request.user}

if __name__ == '__main__':
    app.run(host='localhost', port=8080)
```

## 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)
