From c80354aa2fbfc7a95bef4980470bb26d2930e153 Mon Sep 17 00:00:00 2001 From: mdaneri <17148649+mdaneri@users.noreply.github.com> Date: Wed, 21 Jan 2026 06:53:19 -0800 Subject: [PATCH 1/2] feat(models): support mutualTLS security scheme --- .../Models/OpenApiSecurityScheme.cs | 11 +++++++++++ .../Models/SecuritySchemeType.cs | 7 ++++++- .../V32Tests/OpenApiSecuritySchemeTests.cs | 19 +++++++++++++++++++ .../mutualTlsSecurityScheme.yaml | 2 ++ .../PublicApi/PublicApi.approved.txt | 2 ++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/mutualTlsSecurityScheme.yaml diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index 4bee59619..be389861c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -126,6 +126,9 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version // openIdConnectUrl writer.WriteProperty(OpenApiConstants.OpenIdConnectUrl, OpenIdConnectUrl?.ToString()); break; + case SecuritySchemeType.MutualTLS: + // No additional properties for mutualTLS + break; } // deprecated - serialize as native field for v3.2+ or as extension for earlier versions @@ -170,6 +173,14 @@ public virtual void SerializeAsV2(IOpenApiWriter writer) return; } + if (Type == SecuritySchemeType.MutualTLS) + { + // Bail because V2 does not support mutualTLS + writer.WriteStartObject(); + writer.WriteEndObject(); + return; + } + writer.WriteStartObject(); // type diff --git a/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs b/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs index 6c304597a..5640caa9a 100644 --- a/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs +++ b/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs @@ -26,6 +26,11 @@ public enum SecuritySchemeType /// /// Use OAuth2 with OpenId Connect URL to discover OAuth2 configuration value. /// - [Display("openIdConnect")] OpenIdConnect + [Display("openIdConnect")] OpenIdConnect, + + /// + /// Use mutual TLS authentication. + /// + [Display("mutualTLS")] MutualTLS } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs index e053d7405..ea0937237 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs @@ -102,6 +102,25 @@ public async Task ParseOpenIdConnectSecuritySchemeShouldSucceed() }, securityScheme); } + [Fact] + public async Task ParseMutualTlsSecuritySchemeShouldSucceed() + { + // Act + var securityScheme = await OpenApiModelFactory.LoadAsync( + Path.Combine(SampleFolderPath, "mutualTlsSecurityScheme.yaml"), + OpenApiSpecVersion.OpenApi3_2, + new(), + SettingsFixture.ReaderSettings); + + // Assert + Assert.Equivalent( + new OpenApiSecurityScheme + { + Type = SecuritySchemeType.MutualTLS, + Description = "Sample Description" + }, securityScheme); + } + [Fact] public async Task ParseOAuth2SecuritySchemeWithDeviceAuthorizationUrlShouldSucceed() { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/mutualTlsSecurityScheme.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/mutualTlsSecurityScheme.yaml new file mode 100644 index 000000000..72b4e9ae8 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/mutualTlsSecurityScheme.yaml @@ -0,0 +1,2 @@ +type: mutualTLS +description: Sample Description diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 6a8ad3955..59f1bce31 100644 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -1933,6 +1933,8 @@ namespace Microsoft.OpenApi OAuth2 = 2, [Microsoft.OpenApi.Display("openIdConnect")] OpenIdConnect = 3, + [Microsoft.OpenApi.Display("mutualTLS")] + MutualTLS = 4, } public abstract class SourceExpression : Microsoft.OpenApi.RuntimeExpression { From c45a0bc2feaf809a646f0618841efbeb2ec16a8f Mon Sep 17 00:00:00 2001 From: mdaneri <17148649+mdaneri@users.noreply.github.com> Date: Wed, 21 Jan 2026 07:22:07 -0800 Subject: [PATCH 2/2] fix(writers): throw for mutualTLS in OAS 3.0 --- .../Models/OpenApiSecurityScheme.cs | 5 +++++ .../Models/OpenApiSecuritySchemeTests.cs | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index be389861c..57603e0aa 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -128,6 +128,11 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version break; case SecuritySchemeType.MutualTLS: // No additional properties for mutualTLS + if (version < OpenApiSpecVersion.OpenApi3_1) + { + // mutualTLS is introduced in OpenAPI 3.1 + throw new OpenApiException($"mutualTLS security scheme is only supported in OpenAPI 3.1 and later versions. Current version: {version}"); + } break; } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index 1432d07cc..cd8499b9f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -101,6 +101,12 @@ public class OpenApiSecuritySchemeTests OpenIdConnectUrl = new("https://example.com/openIdConnect") }; + private static OpenApiSecurityScheme MutualTlsSecurityScheme => new() + { + Description = "description1", + Type = SecuritySchemeType.MutualTLS + }; + private static OpenApiSecuritySchemeReference OpenApiSecuritySchemeReference => new("sampleSecurityScheme"); private static OpenApiSecurityScheme ReferencedSecurityScheme => new() { @@ -208,6 +214,19 @@ public async Task SerializeHttpBearerSecuritySchemeAsV3JsonWorks() Assert.Equal(expected, actual); } + [Fact] + public void SerializeMutualTlsSecuritySchemeAsV3Throws() + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter); + + // Act & Assert + var exception = Assert.Throws(() => MutualTlsSecurityScheme.SerializeAsV3(writer)); + Assert.Contains("mutualTLS security scheme is only supported in OpenAPI 3.1 and later versions", exception.Message); + Assert.Contains($"Current version: {OpenApiSpecVersion.OpenApi3_0}", exception.Message); + } + [Fact] public async Task SerializeOAuthSingleFlowSecuritySchemeAsV3JsonWorks() {