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

# Secure Poem APIs with API Key Authentication

Secure your Poem API using a shared secret.

## How Zuplo Handles It

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

## Poem Backend Code

```rust
use poem::{
    handler,
    http::header,
    middleware::Middleware,
    Endpoint, Error, Request, Response, Result, Route,
};
use std::env;
use tokio::sync::OnceCell;
use subtle::ConstantTimeEq;
use poem::web::{Data, DataExtractor};

struct ValidateSharedSecret;

#[poem::async_trait]
impl<E: Endpoint> Middleware<E> for ValidateSharedSecret {
    async fn call(&self, req: Request, ep: E) -> Result<Response> {
        // Load the shared secret from environment variable once
        static SHARED_SECRET: OnceCell<String> = OnceCell::const_new();
        let expected_secret = SHARED_SECRET
            .get_or_try_init(|| async { env::var("SHARED_SECRET").map_err(Error::internal) })
            .await?;

        // Extract the secret from the header
        let secret = req
            .header(header::HeaderName::from_static("x-shared-secret"))
            .ok_or_else(|| Error::from_status(poem::http::StatusCode::UNAUTHORIZED))?;

        // Convert secrets to bytes
        let secret_bytes = secret.as_bytes();
        let expected_bytes = expected_secret.as_bytes();

        // Use constant-time comparison to prevent timing attacks
        if secret_bytes.len() != expected_bytes.len() || !secret_bytes.ct_eq(expected_bytes).unwrap_u8() {
            return Err(Error::from_status(poem::http::StatusCode::UNAUTHORIZED));
        }

        ep.call(req).await
    }
}

#[handler]
async fn protected() -> &'static str {
    "Access granted"
}

#[tokio::main]
async fn main() -> Result<()> {
    dotenv::dotenv().ok(); // Load environment variables from .env file
    let app = Route::new().at("/protected", protected.with(ValidateSharedSecret));
    poem::Server::new(app).bind("127.0.0.1:3000").await?;
    Ok(())
}
```

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