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

# Add JWT Authentication to Your Salvo API

Secure your Salvo API using JWT authentication with JWKS.

## How Zuplo Handles It

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

## Salvo Backend Code

```rust
use salvo::prelude::*;
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use reqwest::Client;
use std::sync::Arc;
use tokio::sync::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::error;

const ISSUER: &str = "https://my-api-a32f34.zuplo.api/__zuplo/issuer";
const JWKS_URI: &str = "https://my-api-a32f34.zuplo.api/__zuplo/issuer/.well-known/jwks.json";

#[derive(Debug, Deserialize)]
struct Jwk {
    kid: String,
    n: String,
    e: String,
}

#[derive(Debug, Deserialize)]
struct Jwks {
    keys: Vec<Jwk>,
}

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

async fn fetch_jwks() -> Result<Jwks, reqwest::Error> {
    let client = Client::new();
    let resp = client.get(JWKS_URI).send().await?;
    Ok(resp.json::<Jwks>().await?)
}

async fn validate_token(token: &str, jwks: &Jwks) -> Result<Claims, String> {
    let header = decode_header(token).map_err(|e| format!("Decode error: {:?}", e))?;
    let kid = match header.kid {
        Some(kid) => kid,
        None => return Err("No `kid` found in token header".into()),
    };

    let jwk = jwks.keys.iter().find(|j| j.kid == kid).ok_or("No matching JWK found")?;
    let decoding_key = DecodingKey::from_rsa_components(&jwk.n, &jwk.e)
        .map_err(|e| format!("Decoding key error: {:?}", e))?;

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

    let data = decode::<Claims>(token, &decoding_key, &validation)
        .map_err(|e| format!("Token decode error: {:?}", e))?;

    Ok(data.claims)
}

struct JwtGuard {
    jwks: Arc<RwLock<Jwks>>,
}

#[async_trait]
impl Handler for JwtGuard {
    async fn handle(&self, req: &mut Request, res: &mut Response) {
        if let Some(auth_header) = req.header::<String>("authorization") {
            if let Some(token) = auth_header.strip_prefix("Bearer ") {
                let jwks_guard = self.jwks.read().await;
                match validate_token(token, &jwks_guard).await {
                    Ok(claims) => {
                        req.extensions_mut().insert(claims);
                        return;
                    }
                    Err(err) => {
                        error!("Token validation error: {}", err);
                        res.set_status_code(StatusCode::UNAUTHORIZED);
                        res.render(Text::Plain("Invalid token".into()));
                        return;
                    }
                };
            }
        }
        res.set_status_code(StatusCode::UNAUTHORIZED);
        res.render(Text::Plain("No token provided".into()));
    }
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();

    let jwks = fetch_jwks().await.expect("Failed to fetch JWKS");
    let jwks = Arc::new(RwLock::new(jwks));

    let router = Router::new()
        .hoop(JwtGuard { jwks: jwks.clone() })
        .handle(protected);

    Server::new(TcpListener::bind("127.0.0.1:7878"))
        .serve(router)
        .await;
}

#[fn_handler]
async fn protected(req: &mut Request, res: &mut Response) {
    if let Some(claims) = req.extensions().get::<Claims>() {
        res.render(Json(claims));
    } else {
        res.set_status_code(StatusCode::UNAUTHORIZED);
        res.render(Text::Plain("Unauthorized".into()));
    }
}
```

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