Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security

Every scheme in components.securitySchemes gets a typed credential struct, named after the scheme key in PascalCase:

SchemeGenerated type
apiKey (in header, query or cookie)pub struct ApiKey(pub String)
http with scheme: bearerpub struct BearerAuth(pub String)
http with scheme: basicpub struct BasicAuth { pub username: String, pub password: String }
oauth2, openIdConnectpub struct OAuth(pub String), the access token sent as Authorization: Bearer <token>

These rules decide which operations require credentials:

  • Document-level security is the default for every operation.
  • An operation’s own security replaces that default.
  • security: [] turns auth off for an operation (listPets in the example spec).
  • If an operation lists several alternatives (an OR), the macro generates an {operation}::Auth enum with one variant per scheme.
  • An empty requirement (security: [{}, {bearerAuth: []}]) makes auth optional: the server passes Option<…> to the handler, with None for an anonymous request, instead of returning 401, and the client sends a credential if one is configured but doesn’t fail without one.
  • An operation that requires an unsupported scheme (such as http with scheme: digest, or mutualTLS) or a scheme that isn’t declared in components.securitySchemes is a compile error, so it is never generated without auth. Unsupported schemes that no operation requires are ignored.

Server side

Secured handlers find the credentials in ctx.auth. The macro extracts the raw credential and returns 401 Unauthorized if it is missing, before your handler runs. Checking that the credential is valid is your job. For oauth2 and openIdConnect that includes checking the token’s scopes against the ones the operation lists:

impl pets::PetsApi for PetServer {
    type Error = pets::NotImplemented;

    async fn list_pets(
        &self,
        ctx: pets::list_pets::Context,
    ) -> Result<pets::list_pets::Response, Self::Error> {
        let pets::list_pets::Context { req, .. } = ctx;
        let store = self.pets.lock().unwrap();
        let limit = req
            .limit
            .map_or(usize::MAX, |n| usize::try_from(n).unwrap_or(0));
        Ok(pets::list_pets::Response::Status200(
            store.iter().take(limit).cloned().collect(),
        ))
    }

    async fn create_pet(
        &self,
        ctx: pets::create_pet::Context,
    ) -> Result<pets::create_pet::Response, Self::Error> {
        let pets::create_pet::Context { req, auth, .. } = ctx;
        // The macro only extracts the token; checking it is up to you.
        if auth.0 != "secret" {
            return Ok(pets::create_pet::Response::Status401);
        }
        let mut store = self.pets.lock().unwrap();
        let pet = pets::Pet {
            id: i64::try_from(store.len()).unwrap_or(i64::MAX) + 1,
            name: req.body.name,
            status: req.body.status,
        };
        store.push(pet.clone());
        drop(store);
        Ok(pets::create_pet::Response::Status201(pet))
    }

    async fn get_pet(
        &self,
        ctx: pets::get_pet::Context,
    ) -> Result<pets::get_pet::Response, Self::Error> {
        let pets::get_pet::Context { req, .. } = ctx;
        let store = self.pets.lock().unwrap();
        Ok(store
            .iter()
            .find(|pet| pet.id == req.pet_id)
            .cloned()
            .map_or(
                pets::get_pet::Response::Status404,
                pets::get_pet::Response::Status200,
            ))
    }
}

With alternatives, match on the generated enum:

async fn get_flex(
    &self,
    ctx: api::get_flex::Context,
) -> Result<api::get_flex::Response, Self::Error> {
    let who = match ctx.auth {
        api::get_flex::Auth::BearerAuth(token) => token.0,
        api::get_flex::Auth::BasicAuth(basic) => basic.username,
    };
    // ...
}

Client side

Clients keep credentials for the whole session. The reqwest carrier holds a {Mod}AuthState, and the {Mod}ClientAuth extension trait adds a with_<scheme>(…) setter for each scheme:

/// Holds everything the generated client implementation needs.
#[derive(Clone, openapi_trait::ReqwestClient)]
struct PetsHttpClient {
    /// The HTTP client used for every request.
    #[openapi_trait(client)]
    http: openapi_trait::reqwest::Client,
    /// Base URL that operation paths are appended to.
    #[openapi_trait(base_url)]
    base_url: String,
    /// Credentials for the spec's security schemes.
    #[openapi_trait(auth)]
    auth: pets::PetsAuthState,
}
use pets::PetsClientAuth as _;

let client = PetsHttpClient { /* … */ }.with_bearer_auth("secret");

The client attaches the right credential to every secured call. If none of an operation’s schemes has a credential, the call fails with MissingCredential before anything is sent. To use different credentials for one call, see per-request headers and auth.

Why the server and client differ

On the server, credentials arrive with each request, so authorization can depend on the caller. On the client, credentials normally belong to the session, so they are set once on the client.