Skip to content

feat(security): explicit security scheme contract, API key extraction, and plugin validation - #32

Merged
rian-be merged 21 commits into
developmentfrom
area/security-schemes
Sep 11, 2026
Merged

rian-be merged 21 commits into
developmentfrom
area/security-schemes

Conversation

@rian-be

@rian-be rian-be commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR extends the AuthKit plugin contract and host with an explicit security scheme model: additive security scheme and API key location enums, host side API key credential extraction, plugin contract validation, and explicit OpenAPI mapping. Every contract value is handled explicitly by the host and by the OpenAPI mapper.

Security Scheme Contract

  • extends AuthKitSecuritySchemeType with MutualTls, Session, Custom, and Basic, and makes OpenIdConnect a distinct value (the historical OAuth2 alias is removed)
  • extends AuthKitApiKeyLocation with GrpcMetadata and Body
  • declares numeric enum values part of the plugin contract: existing values stay unchanged and must never be reused or renumbered
  • stabilizes the scheme enums and metadata so old plugins, including DevTokens, compile and load unmodified

API Key Extraction

  • introduces the API key credential extraction pipeline: header, query, cookie, gRPC metadata, and body location extractors
  • adds JSON and form URL encoded body parsers for body located API keys
  • adds API key credential normalization through ApiKeyValueNormalizer and validator/principal types (IApiKeyValidator, ApiKeyPrincipal)
  • exposes extraction through ApiKeyCredentialExtractor middleware with ApiKeyCredentialExtractorOptions and SecurityServiceCollectionExtensions registration

Plugin Validation

  • introduces PluginContractValidator with supported scheme types and API key locations, validating every declared scheme and location against them
  • defined but unsupported and unknown future values raise InvalidPluginContractException; unknown values are identified by numeric identity and never resolved to Custom or any known value
  • Custom requires plugin supplied Description; missing description produces a validation warning
  • supports per host extensions through PluginContractValidator.CreateCustom

OpenAPI Mapping

  • introduces AuthKitOpenApiSecuritySchemeMapper that maps every value explicitly: semantically correct OpenAPI representations for ApiKey, Http, OAuth2, OpenIdConnect, and Basic
  • throws NotSupportedException for values with no correct 3.0 representation (MutualTls, Session, Custom; GrpcMetadata and Body as API key locations) and ArgumentOutOfRangeException for unknown values
  • RestfulConfiguration catches mapper failures and logs a warning while skipping the definition it never emits a generic substitute and never crashes document generation
  • the host accepts only OpenApi:SpecVersion 3.0; 3.1 is rejected with InvalidOperationException instead of silently emitting a 3.0 document

Host

  • keeps header/query/cookie/gRPC metadata/body credential extraction supported at runtime through AuthenticateApiKey counterparts even though OpenAPI 3.0 cannot describe some of them
  • runtime support and OpenAPI representability are orthogonal: supported locations extract at runtime (ADR-017) though OpenAPI 3.0 cannot describe body/gRPC metadata
  • refractors OpenAPI serving configuration to consume the mapper

Tests

  • adds host and plugin security scheme tests covering every contract value
  • prevents OAuth2 and OpenID Connect enum aliasing regression
  • adds security-scheme tests and updates package configuration
  • covers mandated/unknown value handling and no-fallback behavior

Documentation

  • records the security scheme decisions as ADRs (ADR-017 API key credential extraction strategies, ADR-018 security scheme contract explicit handling)

Result

Two explicit contract guarantees now exist: no silent fallback between schemes or locations, and unknown future values are always rejected. Plugins that declare unsupported mechanisms fail contract validation at startup; per-host extensions use PluginContractValidator.CreateCustom. Old plugins compile and load unmodified, and persisted descriptors keep their meaning.

Closes #4
Closes #5
Closes #6
Closes #7

@rian-be rian-be self-assigned this Sep 11, 2026
@rian-be
rian-be marked this pull request as draft September 11, 2026 11:12
@rian-be rian-be added P0 Contract foundation area/security-schemes Security scheme enums (Section F) area/host Host-side runtime (DI, OpenAPI, health exec) contract Changes the plugin contract additive Additive, non-breaking change sub-task Child task of an epic epic Parent/umbrella issue labels Sep 11, 2026
@rian-be
rian-be marked this pull request as ready for review September 11, 2026 11:18
Address PR review: Name must stay a transport-agnostic scheme identity and
must not double as the credential transport field. Add CredentialName to
AuthKitSecuritySchemeDescriptor; the OpenAPI mapper and every location
extractor fall back to location-specific defaults (X-Api-Key, api_key).

Also addressed by the same review:
- MaxBodySize is a hard read limit for Body extraction, independent of the
  BufferThreshold spill threshold.
- StripBearerPrefix is opt-in instead of unconditional.
- Custom authentication wording in ADR-018 is a warning, not a mapping rule.
- Bump Swashbuckle to 10.2.3 / Microsoft.OpenApi 2.7.x so the mapper can use
  OpenApi3_1 and SecuritySchemeType.MutualTLS; fix namespaces moved by the
  development merge.
Comment thread src/Host/Security/LocationExtractors/BodyApiKeyLocationExtractor.cs Fixed
Comment thread src/Host/Plugins/PluginLoader.cs Fixed
Comment thread src/Host/Plugins/PluginLoader.cs Fixed
Comment thread src/Host/Security/Middleware/ApiKeyCredentialExtractor.cs Fixed
Comment thread src/Host/Security/Middleware/ApiKeyCredentialExtractor.cs Fixed
… credentials

Replace ambient GetService<AuthKitSecuritySchemeDescriptor>() with request-aware
resolution driven by endpoint metadata and a host-level scheme registry built
from the security schemes contributed by loaded plugins.

- Add SecuritySchemeAttribute endpoint metadata (Abstractions contract).
- Add ISecuritySchemeRegistry/SecuritySchemeRegistry; duplicate scheme names
  across plugins are a startup configuration error.
- Middleware now resolves the endpoint's scheme, rejects unknown schemes, and
  fails closed with 401 Unauthorized when a credential is present but invalid
  or cannot be extracted. Requests without a credential still continue so
  downstream middleware can decide how to handle them.
- Register ApiKeyCredentialExtractor in the pipeline and wire
  AddApiKeyCredentialExtraction into host startup.

Closes review feedback requesting request-aware scheme resolution and a
no-credential vs. invalid-credential distinction.
Comment thread src/Host/Security/Middleware/ApiKeyCredentialExtractor.cs Fixed
rian-be and others added 4 commits September 11, 2026 18:00
Docker base image mcr.microsoft.com/dotnet/sdk:10.0 resolves a newer 10.0
feature band (10.0.400) than the pinned global.json (10.0.110). latestPatch
does not cross feature bands, so dotnet build in the container failed with
'Requested SDK version 10.0.110'. Use latestFeature, which stays within
major.minor 10.0 but accepts any installed 10.0 feature band.
…from user input'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…ath.Combine' may silently drop its earlier arguments'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
rian-be and others added 3 commits September 11, 2026 18:58
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@rian-be
rian-be merged commit fe9e04e into development Sep 11, 2026
6 of 8 checks passed
@rian-be
rian-be deleted the area/security-schemes branch September 11, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

additive Additive, non-breaking change area/host Host-side runtime (DI, OpenAPI, health exec) area/security-schemes Security scheme enums (Section F) contract Changes the plugin contract epic Parent/umbrella issue P0 Contract foundation sub-task Child task of an epic

Projects

None yet

2 participants