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

# Add JWT Authentication to Your Litestar API

Secure your Litestar API using JWT authentication with JWKS.

## How Zuplo Handles It

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

## Litestar Backend Code

```python
from litestar import Litestar, get, Request
from litestar.middleware import AbstractMiddleware
from litestar.exceptions import NotAuthorizedException
from jose import jwt
from jose.exceptions import JWTError, ExpiredSignatureError
import requests
from cachetools import TTLCache
from typing import Any

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: str):
    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")

async def jwt_auth_middleware(request: Request, call_next: Any) -> Any:
    auth_header = request.headers.get('Authorization')
    if not auth_header or not auth_header.startswith('Bearer '):
        raise NotAuthorizedException(detail='No token provided')

    token = auth_header[7:]
    try:
        key = get_key(token)
        payload = jwt.decode(token, key, algorithms=ALGORITHMS, issuer=ISSUER)
        request.state.user = payload
    except ExpiredSignatureError:
        raise NotAuthorizedException(detail='Token has expired')
    except JWTError as e:
        raise NotAuthorizedException(detail=f'Invalid token: {str(e)}')

    return await call_next(request)

@get('/protected')
async def protected_route(request: Request) -> dict:
    return {'message': 'Access granted', 'user': request.state.user}

app = Litestar(
    route_handlers=[protected_route],
    middleware=[jwt_auth_middleware]
)

if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app, host='0.0.0.0', port=8000)
```

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