Skip to content

[BUG] [rust-server] Untyped from_headers in generated context.rs matches Bearer tokens in Basic-only auth blocks, bypassing later API-key security schemes #24095

Description

@twistali

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

The generated context.rs authentication middleware in the rust-server generator uses swagger::auth::from_headers(headers) for isBasicBasic security scheme blocks. This function matches both Basic and Bearer Authorization headers (it parses the header value case-insensitively for either scheme prefix). Each matched block does an early return, short-circuiting evaluation of any later security scheme blocks.

This means that for any API defining multiple security schemes where HTTP Basic auth is listed before an API key (in-header) scheme, a request carrying Authorization: Bearer <token> will be greedily captured by the Basic auth block and returned immediately — the API key block is never reached.

Impact: In deployments where the Bearer token path has a permissive fallback (e.g. AllowAllAuthenticator when OAuth is not configured), this is a silent authorization bypass. Requests that should be authenticated via the API key header are instead authorized via the permissive Bearer fallback. The API key / client certificate is never evaluated.

This is a regression from the previous typed extraction approach which used swagger::auth::from_headers::<Basic>(headers) — a scheme-typed generic that only matched Basic headers.

openapi-generator version

Present across all 7.x releases

OpenAPI declaration file content or url
openapi: "3.0.3"
info:
  title: Multi-Auth Example
  version: "1.0.0"
paths:
  /resource:
    get:
      operationId: getResource
      security:
        - BasicAuth: []
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: string
        '403':
          description: Forbidden
components:
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-client-cert
    BearerAuth:
      type: http
      scheme: bearer

Any spec defining both type: http, scheme: basic and type: apiKey, in: header security schemes on the same operation triggers this bug.

Generation Details

No special options required — the default rust-server generator reproduces the issue.

Steps to reproduce
  1. Generate a Rust server from the above spec using the rust-server generator
  2. Open the generated src/context.rs
  3. Observe the generated auth extraction in the Service<Request<ReqBody>> impl for AddContext:

Actual output (generated context.rs):

// Block 1: intended for Basic auth only
{
    use std::ops::Deref;
    if let Some(auth) = swagger::auth::from_headers(headers) {  // matches Basic OR Bearer!
        let context = context.push(Some(auth));
        return self.inner.call((request, context))  // early return
    }
}
// Block 2: API key from header — NEVER REACHED for Bearer requests
{
    use swagger::auth::api_key_from_header;
    if let Some(header) = api_key_from_header(headers, "x-client-cert") {
        let auth_data = AuthData::ApiKey(header);
        let context = context.push(Some(auth_data));
        return self.inner.call((request, context))
    }
}
  1. Send a request with Authorization: Bearer <token> and an x-client-cert header
  2. The Bearer token is captured by Block 1 and returned immediately — the API key block is never evaluated

Expected output (what the template should generate for Basic-only blocks):

// Block 1: Basic auth only — should not match Bearer
{
    use swagger::auth::basic_from_headers;  // or a typed/filtered variant
    if let Some(auth) = swagger::auth::basic_from_headers(headers) {  // matches Basic ONLY
        let context = context.push(Some(auth));
        return self.inner.call((request, context))
    }
}
// Block 2: API key from header — now correctly reachable for Bearer requests
{
    use swagger::auth::api_key_from_header;
    if let Some(header) = api_key_from_header(headers, "x-client-cert") {
        let auth_data = AuthData::ApiKey(header);
        let context = context.push(Some(auth_data));
        return self.inner.call((request, context))
    }
}
Related issues/PRs
Suggest a fix

The context.mustache template needs to emit scheme-specific extraction for isBasicBasic blocks. Two possible approaches:

Option A: Add a basic_from_headers function to the swagger crate that only matches Basic:

pub fn basic_from_headers(headers: &HeaderMap) -> Option<AuthData> {
    headers.get(AUTHORIZATION).and_then(|value| {
        if let Ok(value_str) = value.to_str() {
            if value_str.to_lowercase().starts_with("basic ") {
                Basic::decode(value).map(|basic| {
                    AuthData::Basic(basic.username().to_string(), basic.password().to_string())
                })
            } else {
                None
            }
        } else {
            None
        }
    })
}

Then update the {{#isBasicBasic}} block in context.mustache to call basic_from_headers instead of from_headers.

Option B: Filter in the template itself — after calling from_headers, check that the returned AuthData variant matches the expected scheme before returning:

{{#isBasicBasic}}
{
    if let Some(auth @ AuthData::Basic(..)) = swagger::auth::from_headers(headers) {
        let context = context.push(Some(auth));
        return self.inner.call((request, context))
    }
}
{{/isBasicBasic}}

Similarly, the {{#isBasicBearer}} and {{#isOAuth}} blocks should filter for AuthData::Bearer(..) only.

Either approach restores the scheme-specific behavior that existed in swagger 5/6 and ensures API key / client certificate blocks remain reachable when a Bearer token is present.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions