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

# Add JWT Authentication to Your Poem API

Secure your Poem API using JWT authentication with JWKS.

## How Zuplo Handles It

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

## Poem Backend Code

```rust
use poem::{
    error::InternalServerError,
    handler,
    listener::TcpListener,
    middleware::AddData,
    web::{Data, Request, Response},
    Route, Server,
};
use poem_openapi::{
    auth::ApiKey,
    payload::Json,
    types::{Email, Password},
    OpenApi, OpenApiService,
};
use serde::{Deserialize, Serialize};
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation, jwk::{Jwk, JwkSet}};
use reqwest::blocking::Client;
use std::sync::{Arc, RwLock};

// Replace with your actual Zuplo deployment name or custom domain
const ISSUER: &str = "https://my-api-a32f34.zuplo.api/__zuplo/issuer";

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    company: String,
    exp: usize,
}

struct Api;

// Middleware to validate JWT
fn validate_jwt(req: &Request, data: &Arc<RwLock<JwkSet>>) -> poem::Result<()> {
    // Extract token from headers
    let token = match req.headers().get("authorization") {
        Some(auth) => auth.to_str().unwrap().replace("Bearer ", ""),
        None => return Err(InternalServerError.into())
    };

    // Decode the JWT header to get the "kid"
    let header = decode_header(&token).map_err(|_| InternalServerError)?;

    // Find the key in the JWKS matching the "kid"
    let kid = header.kid.ok_or_else(|| InternalServerError)?;
    let jwk_set = data.read().unwrap();

    let jwk = jwk_set.keys.iter().find(|j| j.common.kid == Some(kid.clone()))
        .ok_or_else(|| InternalServerError)?;

    let decoding_key = DecodingKey::from_rsa_pem(jwk.rsa.as_ref().unwrap()).unwrap();

    // Verify token
    let mut validation = Validation::new(Algorithm::RS256);
    validation.set_issuer(&[ISSUER]);

    let _decoded = decode::<Claims>(&token, &decoding_key, &validation)
        .map_err(|_| InternalServerError)?;

    Ok(())
}

#[handler]
async fn protected_endpoint(req: &Request, data: Data<&Arc<RwLock<JwkSet>>>) -> poem::Result<Json<Claims>> {
    validate_jwt(req, data.get_ref())?;
    Ok(Json(Claims {
        sub: "user".to_string(),
        company: "example".to_string(),
        exp: 1000000000
    }))
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    // Fetch the JWKS
    let response: JwkSet = Client::new()
        .get(&format!("{ISSUER}/.well-known/jwks.json"))
        .send()
        .unwrap()
        .json()
        .unwrap();

    // Arc and RwLock to share JWKS data across requests
    let jwk_set = Arc::new(RwLock::new(response));

    // Create the Poem server with middleware
    let app = Route::new()
        .at("/protected", protected_endpoint)
        .with(AddData::new(jwk_set));

    let server = Server::new(TcpListener::bind("127.0.0.1:3000"));

    server.run(app).await
}
```

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