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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions