---
title: "Secure Salvo APIs with API Key Authentication"
description: "Secure your Salvo API using a shared secret."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/rust/salvo/secure-header"
framework: "Salvo"
language: "Rust"
authStrategy: "shared secret header"
pageType: use-case
---

# Secure Salvo APIs with API Key Authentication

Secure your Salvo API using a shared secret.

## How Zuplo Handles It

Put Zuplo in front of your Salvo backend to authenticate API keys and forward a shared secret header so your origin only accepts traffic from Zuplo.

## Salvo Backend Code

```rust
use salvo::prelude::*;
use std::env;
use std::time::Duration;
use tokio::time::sleep;

struct SharedSecretGuard;

#[async_trait]
impl Handler for SharedSecretGuard {
    async fn handle(&self, ctx: &mut Request) -> Result<()> {
        let expected_secret = env::var("SHARED_SECRET").map_err(|_| {
            ctx.set_status_code(StatusCode::INTERNAL_SERVER_ERROR);
            ctx.render_plain_text("Server configuration error");
            Error::from(HttpServerError::from("Missing SHARED_SECRET environment variable"))
        })?;

        let secret = ctx.header::<String>("x-shared-secret").ok_or_else(|| {
            ctx.set_status_code(StatusCode::UNAUTHORIZED);
            ctx.render_plain_text("No secret provided");
            Error::from(HttpClientError::from("Missing x-shared-secret header"))
        })?;

        // Timing-safe comparison
        if secret.len() != expected_secret.len() {
            ctx.set_status_code(StatusCode::UNAUTHORIZED);
            ctx.render_plain_text("Invalid secret");
            return Err(Error::from(HttpClientError::from("Invalid secret length")));
        }

        if !constant_time_eq(secret.as_bytes(), expected_secret.as_bytes()) {
            ctx.set_status_code(StatusCode::UNAUTHORIZED);
            ctx.render_plain_text("Invalid secret");
            return Err(Error::from(HttpClientError::from("Invalid secret content")));
        }

        Ok(())
    }
}

pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
    if a.len() != b.len() {
        return false;
    }

    let mut result: u8 = 0;
    for (x, y) in a.iter().zip(b.iter()) {
        result |= x ^ y;
    }

    result == 0
}

#[fn_handler]
async fn protected_endpoint(res: &mut Response) {
    res.render_plain_text("Access granted");
}

#[tokio::main]
async fn main() {
    let router = Router::with_path("protected")
        .hoop(SharedSecretGuard)
        .get(protected_endpoint);

    Server::new(TcpListener::bind("127.0.0.1:7878"))
        .serve(router)
        .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)
