diff --git a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSecurityScheme.cs
index 41247ce08..16da4a881 100644
--- a/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSecurityScheme.cs
+++ b/src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSecurityScheme.cs
@@ -46,6 +46,12 @@ public interface IOpenApiSecurityScheme : IOpenApiDescribedElement, IOpenApiRead
///
public Uri? OpenIdConnectUrl { get; }
+ ///
+ /// URL to the OAuth2 Authorization Server Metadata document (RFC 8414).
+ /// Note: This field is supported in OpenAPI 3.2.0+ only.
+ ///
+ public Uri? OAuth2MetadataUrl { get; }
+
///
/// Specifies that a security scheme is deprecated and SHOULD be transitioned out of usage.
/// Note: This field is supported in OpenAPI 3.2.0+. For earlier versions, it will be serialized as x-oai-deprecated extension.
diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs
index b87c9079d..543baae29 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs
@@ -695,6 +695,11 @@ public static class OpenApiConstants
///
public const string Flows = "flows";
+ ///
+ /// Field: Oauth2MetadataUrl
+ ///
+ public const string OAuth2MetadataUrl = "oauth2MetadataUrl";
+
///
/// Field: OpenIdConnectUrl
///
diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs
index 57603e0aa..b1670de6d 100644
--- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs
+++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs
@@ -35,6 +35,9 @@ public class OpenApiSecurityScheme : IOpenApiExtensible, IOpenApiSecurityScheme
///
public Uri? OpenIdConnectUrl { get; set; }
+ ///
+ public Uri? OAuth2MetadataUrl { get; set; }
+
///
public bool Deprecated { get; set; }
@@ -60,6 +63,7 @@ internal OpenApiSecurityScheme(IOpenApiSecurityScheme securityScheme)
BearerFormat = securityScheme.BearerFormat ?? BearerFormat;
Flows = securityScheme.Flows != null ? new(securityScheme.Flows) : null;
OpenIdConnectUrl = securityScheme.OpenIdConnectUrl != null ? new Uri(securityScheme.OpenIdConnectUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
+ OAuth2MetadataUrl = securityScheme.OAuth2MetadataUrl != null ? new Uri(securityScheme.OAuth2MetadataUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
Deprecated = securityScheme.Deprecated;
Extensions = securityScheme.Extensions != null ? new Dictionary(securityScheme.Extensions) : null;
}
@@ -118,7 +122,16 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version
break;
case SecuritySchemeType.OAuth2:
// This property apply to oauth2 type only.
+ // oauth2MetadataUrl
// flows
+ if (version >= OpenApiSpecVersion.OpenApi3_2)
+ {
+ writer.WriteProperty(OpenApiConstants.OAuth2MetadataUrl, OAuth2MetadataUrl?.ToString());
+ }
+ else
+ {
+ writer.WriteProperty("x-oauth2-metadata-url", OAuth2MetadataUrl?.ToString());
+ }
writer.WriteOptionalObject(OpenApiConstants.Flows, Flows, callback);
break;
case SecuritySchemeType.OpenIdConnect:
diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs
index 4267315e3..25ed6e25f 100644
--- a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs
+++ b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs
@@ -54,6 +54,9 @@ public string? Description
///
public Uri? OpenIdConnectUrl { get => Target?.OpenIdConnectUrl; }
+ ///
+ public Uri? OAuth2MetadataUrl { get => Target?.OAuth2MetadataUrl; }
+
///
public IDictionary? Extensions { get => Target?.Extensions; }
diff --git a/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs
index 34e3f589e..f0acc7ea6 100644
--- a/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs
+++ b/src/Microsoft.OpenApi/Reader/V32/OpenApiSecuritySchemeDeserializer.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
@@ -68,6 +68,16 @@ internal static partial class OpenApiV32Deserializer
}
}
},
+ {
+ "oauth2MetadataUrl", (o, n, _) =>
+ {
+ var metadataUrl = n.GetScalarValue();
+ if (metadataUrl != null)
+ {
+ o.OAuth2MetadataUrl = new(metadataUrl, UriKind.RelativeOrAbsolute);
+ }
+ }
+ },
{
"flows", (o, n, t) =>
{
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs
index ea0937237..50c836f66 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs
@@ -86,6 +86,36 @@ public async Task ParseOAuth2SecuritySchemeShouldSucceed()
}, securityScheme);
}
+ [Fact]
+ public async Task ParseOAuth2SecuritySchemeWithMetadataUrlShouldSucceed()
+ {
+ // Act
+ var securityScheme = await OpenApiModelFactory.LoadAsync(
+ Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithMetadataUrl.yaml"),
+ OpenApiSpecVersion.OpenApi3_2,
+ new(),
+ SettingsFixture.ReaderSettings);
+
+ // Assert
+ Assert.Equivalent(
+ new OpenApiSecurityScheme
+ {
+ Type = SecuritySchemeType.OAuth2,
+ OAuth2MetadataUrl = new Uri("https://idp.example.com/.well-known/oauth-authorization-server"),
+ Flows = new OpenApiOAuthFlows
+ {
+ ClientCredentials = new OpenApiOAuthFlow
+ {
+ TokenUrl = new Uri("https://idp.example.com/oauth/token"),
+ Scopes = new System.Collections.Generic.Dictionary
+ {
+ ["scope:one"] = "Scope one"
+ }
+ }
+ }
+ }, securityScheme);
+ }
+
[Fact]
public async Task ParseOpenIdConnectSecuritySchemeShouldSucceed()
{
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/oauth2SecuritySchemeWithMetadataUrl.yaml b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/oauth2SecuritySchemeWithMetadataUrl.yaml
new file mode 100644
index 000000000..612f65fde
--- /dev/null
+++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/Samples/OpenApiSecurityScheme/oauth2SecuritySchemeWithMetadataUrl.yaml
@@ -0,0 +1,8 @@
+# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.2.0.md#securitySchemeObject
+type: oauth2
+oauth2MetadataUrl: https://idp.example.com/.well-known/oauth-authorization-server
+flows:
+ clientCredentials:
+ tokenUrl: https://idp.example.com/oauth/token
+ scopes:
+ scope:one: Scope one
diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs
index cd8499b9f..b826c89b3 100644
--- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs
@@ -93,6 +93,24 @@ public class OpenApiSecuritySchemeTests
}
};
+ private static OpenApiSecurityScheme OAuth2MetadataSecurityScheme => new()
+ {
+ Description = "description1",
+ Type = SecuritySchemeType.OAuth2,
+ OAuth2MetadataUrl = new("https://idp.example.com/.well-known/oauth-authorization-server"),
+ Flows = new()
+ {
+ ClientCredentials = new()
+ {
+ TokenUrl = new("https://idp.example.com/oauth/token"),
+ Scopes = new Dictionary
+ {
+ ["scope:one"] = "Scope one"
+ }
+ }
+ }
+ };
+
private static OpenApiSecurityScheme OpenIdConnectSecurityScheme => new()
{
Description = "description1",
@@ -257,6 +275,62 @@ public async Task SerializeOAuthSingleFlowSecuritySchemeAsV3JsonWorks()
Assert.Equal(expected, actual);
}
+ [Fact]
+ public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV32JsonWorks()
+ {
+ // Arrange
+ var expected =
+ """
+ {
+ "type": "oauth2",
+ "description": "description1",
+ "oauth2MetadataUrl": "https://idp.example.com/.well-known/oauth-authorization-server",
+ "flows": {
+ "clientCredentials": {
+ "tokenUrl": "https://idp.example.com/oauth/token",
+ "scopes": {
+ "scope:one": "Scope one"
+ }
+ }
+ }
+ }
+ """;
+
+ // Act
+ var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
+
+ // Assert
+ Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
+ }
+
+ [Fact]
+ public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV31JsonOmitsMetadataUrl()
+ {
+ // Arrange
+ var expected =
+ """
+ {
+ "type": "oauth2",
+ "description": "description1",
+ "x-oauth2-metadata-url": "https://idp.example.com/.well-known/oauth-authorization-server",
+ "flows": {
+ "clientCredentials": {
+ "tokenUrl": "https://idp.example.com/oauth/token",
+ "scopes": {
+ "scope:one": "Scope one"
+ }
+ }
+ }
+ }
+ """;
+
+ // Act
+ var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1);
+
+ // Assert
+ Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual)));
+ }
+
[Fact]
public async Task SerializeOAuthMultipleFlowSecuritySchemeAsV3JsonWorks()
{
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 5413879c0..b1c9810e5 100644
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -288,6 +288,7 @@ namespace Microsoft.OpenApi
Microsoft.OpenApi.OpenApiOAuthFlows? Flows { get; }
Microsoft.OpenApi.ParameterLocation? In { get; }
string? Name { get; }
+ System.Uri? OAuth2MetadataUrl { get; }
System.Uri? OpenIdConnectUrl { get; }
string? Scheme { get; }
Microsoft.OpenApi.SecuritySchemeType? Type { get; }
@@ -538,6 +539,7 @@ namespace Microsoft.OpenApi
public const string Null = "null";
public const string Nullable = "nullable";
public const string NullableExtension = "x-nullable";
+ public const string OAuth2MetadataUrl = "oauth2MetadataUrl";
public const string OneOf = "oneOf";
public const string OpenApi = "openapi";
public const string OpenIdConnectUrl = "openIdConnectUrl";
@@ -1381,6 +1383,7 @@ namespace Microsoft.OpenApi
public Microsoft.OpenApi.OpenApiOAuthFlows? Flows { get; set; }
public Microsoft.OpenApi.ParameterLocation? In { get; set; }
public string? Name { get; set; }
+ public System.Uri? OAuth2MetadataUrl { get; set; }
public System.Uri? OpenIdConnectUrl { get; set; }
public string? Scheme { get; set; }
public Microsoft.OpenApi.SecuritySchemeType? Type { get; set; }
@@ -1400,6 +1403,7 @@ namespace Microsoft.OpenApi
public Microsoft.OpenApi.OpenApiOAuthFlows? Flows { get; }
public Microsoft.OpenApi.ParameterLocation? In { get; }
public string? Name { get; }
+ public System.Uri? OAuth2MetadataUrl { get; }
public System.Uri? OpenIdConnectUrl { get; }
public string? Scheme { get; }
public Microsoft.OpenApi.SecuritySchemeType? Type { get; }