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

# Add JWT Authentication to Your BlackSheep API

Secure your BlackSheep API using JWT authentication with JWKS.

## How Zuplo Handles It

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

## BlackSheep Backend Code

```python
import asyncio
from blacksheep import Application, FromHeader, HTTPResponse, json
from blacksheep.server.authentication import AuthenticationHandler
from jose import jwt
from cryptography.hazmat.primitives import serialization
import httpx

ISSUER = "https://my-api-a32f34.zuplo.api/__zuplo/issuer"
JWKS_URI = f"{ISSUER}/.well-known/jwks.json"

class JWTAuthentication(AuthenticationHandler):
    def __init__(self):
        self.jwks = None
        self.algorithms = ["RS256"]

    async def load_jwks(self):
        async with httpx.AsyncClient() as client:
            response = await client.get(JWKS_URI)
            response.raise_for_status()
            self.jwks = response.json()

    async def get_public_key(self, kid):
        if not self.jwks:
            await self.load_jwks()
        for key in self.jwks['keys']:
            if key['kid'] == kid:
                return serialization.load_pem_public_key(
                    key['x5c'][0].encode()
                )
        raise ValueError("Public key not found.")

    async def authenticate(self, ctx):
        auth_header = ctx.request.headers.get(b'Authorization')
        if not auth_header or not auth_header.startswith(b'Bearer '):
            return None

        token = auth_header[7:].decode()
        unverified_header = jwt.get_unverified_header(token)
        try:
            public_key = await self.get_public_key(unverified_header['kid'])
            payload = jwt.decode(
                token,
                public_key,
                algorithms=self.algorithms,
                issuer=ISSUER
            )
            return payload
        except Exception as e:
            raise HTTPResponse(status=401, text=f"Unauthorized: {str(e)}")

app = Application()

@app.route("/protected")
async def protected(user_info=FromHeader(JWTAuthentication())):
    return json({"message": "Access granted", "user": user_info})

if __name__ == "__main__":
    asyncio.run(app.start())
```

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