Security
Every scheme in components.securitySchemes gets a typed credential struct,
named after the scheme key in PascalCase:
| Scheme | Generated type |
|---|---|
apiKey (in header, query or cookie) | pub struct ApiKey(pub String) |
http with scheme: bearer | pub struct BearerAuth(pub String) |
http with scheme: basic | pub struct BasicAuth { pub username: String, pub password: String } |
oauth2, openIdConnect | pub struct OAuth(pub String), the access token sent as Authorization: Bearer <token> |
These rules decide which operations require credentials:
- Document-level
securityis the default for every operation. - An operation’s own
securityreplaces that default. security: []turns auth off for an operation (listPetsin the example spec).- If an operation lists several alternatives (an OR), the macro generates an
{operation}::Authenum with one variant per scheme. - An empty requirement (
security: [{}, {bearerAuth: []}]) makes auth optional: the server passesOption<…>to the handler, withNonefor 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
httpwithscheme: digest, ormutualTLS) or a scheme that isn’t declared incomponents.securitySchemesis 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.