diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs
index 3d41d1c2c5..2239084555 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs
@@ -4,6 +4,7 @@
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Swashbuckle.AspNetCore.SwaggerGen;
+using Microsoft.AspNetCore.Authentication;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -308,6 +309,22 @@ public static void SupportNonNullableReferenceTypes(this SwaggerGenOptions swagg
swaggerGenOptions.SchemaGeneratorOptions.SupportNonNullableReferenceTypes = true;
}
+ ///
+ /// Automatically infer security schemes from authentication/authorization state in ASP.NET Core.
+ ///
+ ///
+ ///
+ /// Provide alternative implementation that maps ASP.NET Core Authentication schemes to Open API security schemes
+ ///
+ /// Currently only supports JWT Bearer authentication
+ public static void InferSecuritySchemes(
+ this SwaggerGenOptions swaggerGenOptions,
+ Func, IDictionary> securitySchemesSelector = null)
+ {
+ swaggerGenOptions.SwaggerGeneratorOptions.InferSecuritySchemes = true;
+ swaggerGenOptions.SwaggerGeneratorOptions.SecuritySchemesSelector = securitySchemesSelector;
+ }
+
///
/// Extend the Swagger Generator with "filters" that can modify Schemas after they're initially generated
///
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs
index 8fe62db4a4..d20fbf83a5 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs
@@ -34,26 +34,48 @@ public SwaggerGenerator(
SwaggerGeneratorOptions options,
IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
ISchemaGenerator schemaGenerator,
- IAuthenticationSchemeProvider authentiationSchemeProvider) : this(options, apiDescriptionsProvider, schemaGenerator)
+ IAuthenticationSchemeProvider authenticationSchemeProvider) : this(options, apiDescriptionsProvider, schemaGenerator)
{
- _authenticationSchemeProvider = authentiationSchemeProvider;
+ _authenticationSchemeProvider = authenticationSchemeProvider;
}
public async Task GetSwaggerAsync(string documentName, string host = null, string basePath = null)
{
- var (applicableApiDescriptions, swaggerDoc, schemaRepository) = GetSwaggerDocument(documentName, host, basePath);
+ var (applicableApiDescriptions, swaggerDoc, schemaRepository) = GetSwaggerDocumentWithoutFilters(documentName, host, basePath);
+
swaggerDoc.Components.SecuritySchemes = await GetSecuritySchemes();
+
+ // NOTE: Filter processing moved here so they may effect generated security schemes
+ var filterContext = new DocumentFilterContext(applicableApiDescriptions, _schemaGenerator, schemaRepository);
+ foreach (var filter in _options.DocumentFilters)
+ {
+ filter.Apply(swaggerDoc, filterContext);
+ }
+
+ swaggerDoc.Components.Schemas = new SortedDictionary(swaggerDoc.Components.Schemas, _options.SchemaComparer);
+
return swaggerDoc;
}
public OpenApiDocument GetSwagger(string documentName, string host = null, string basePath = null)
{
- var (applicableApiDescriptions, swaggerDoc, schemaRepository) = GetSwaggerDocument(documentName, host, basePath);
+ var (applicableApiDescriptions, swaggerDoc, schemaRepository) = GetSwaggerDocumentWithoutFilters(documentName, host, basePath);
+
swaggerDoc.Components.SecuritySchemes = GetSecuritySchemes().Result;
+
+ // NOTE: Filter processing moved here so they may effect generated security schemes
+ var filterContext = new DocumentFilterContext(applicableApiDescriptions, _schemaGenerator, schemaRepository);
+ foreach (var filter in _options.DocumentFilters)
+ {
+ filter.Apply(swaggerDoc, filterContext);
+ }
+
+ swaggerDoc.Components.Schemas = new SortedDictionary(swaggerDoc.Components.Schemas, _options.SchemaComparer);
+
return swaggerDoc;
}
- private (IEnumerable, OpenApiDocument, SchemaRepository) GetSwaggerDocument(string documentName, string host = null, string basePath = null)
+ private (IEnumerable, OpenApiDocument, SchemaRepository) GetSwaggerDocumentWithoutFilters(string documentName, string host = null, string basePath = null)
{
if (!_options.SwaggerDocs.TryGetValue(documentName, out OpenApiInfo info))
throw new UnknownSwaggerDocument(documentName, _options.SwaggerDocs.Select(d => d.Key));
@@ -77,38 +99,37 @@ public OpenApiDocument GetSwagger(string documentName, string host = null, strin
SecurityRequirements = new List(_options.SecurityRequirements)
};
- var filterContext = new DocumentFilterContext(applicableApiDescriptions, _schemaGenerator, schemaRepository);
- foreach (var filter in _options.DocumentFilters)
- {
- filter.Apply(swaggerDoc, filterContext);
- }
-
- swaggerDoc.Components.Schemas = new SortedDictionary(swaggerDoc.Components.Schemas, _options.SchemaComparer);
-
return (applicableApiDescriptions, swaggerDoc, schemaRepository);
}
- private async Task> GetSecuritySchemes()
+ private async Task> GetSecuritySchemes()
{
- var securitySchemes = new Dictionary(_options.SecuritySchemes);
- var authenticationSchemes = Enumerable.Empty();
- if (_authenticationSchemeProvider is not null)
+ if (!_options.InferSecuritySchemes)
{
- authenticationSchemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
+ return new Dictionary(_options.SecuritySchemes);
}
- var securitySchemesFromSelector = _options.SecuritySchemesSelector(authenticationSchemes);
- // Favor security schemes set via options over those generated
- // from the selector. For the default selector, this effectively
- // ends up favoring `Bearer` authentication types explicitly set
- // by the user over those derived by the selector.
- foreach (var securityScheme in securitySchemesFromSelector)
+
+ var authenticationSchemes = (_authenticationSchemeProvider is not null)
+ ? await _authenticationSchemeProvider.GetAllSchemesAsync()
+ : Enumerable.Empty();
+
+ if (_options.SecuritySchemesSelector != null)
{
- if (!securitySchemes.ContainsKey(securityScheme.Key))
- {
- securitySchemes.Add(securityScheme.Key, securityScheme.Value);
- }
+ return _options.SecuritySchemesSelector(authenticationSchemes);
}
- return securitySchemes;
+
+ // Default implementation, currently only supports JWT Bearer scheme
+ return authenticationSchemes
+ .Where(authScheme => authScheme.Name == "Bearer")
+ .ToDictionary(
+ (authScheme) => authScheme.Name,
+ (authScheme) => new OpenApiSecurityScheme
+ {
+ Type = SecuritySchemeType.Http,
+ Scheme = "bearer", // "bearer" refers to the header name here
+ In = ParameterLocation.Header,
+ BearerFormat = "Json Web Token"
+ });
}
private IList GenerateServers(string host, string basePath)
diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs
index b4ba1f9c4c..48183d3cea 100644
--- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs
+++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs
@@ -20,7 +20,7 @@ public SwaggerGeneratorOptions()
OperationIdSelector = DefaultOperationIdSelector;
TagsSelector = DefaultTagsSelector;
SortKeySelector = DefaultSortKeySelector;
- SecuritySchemesSelector = DefaultSecuritySchemeSelector;
+ SecuritySchemesSelector = null;
SchemaComparer = StringComparer.Ordinal;
Servers = new List();
SecuritySchemes = new Dictionary();
@@ -45,6 +45,10 @@ public SwaggerGeneratorOptions()
public Func SortKeySelector { get; set; }
+ public bool InferSecuritySchemes { get; set; }
+
+ public Func, IDictionary> SecuritySchemesSelector { get; set;}
+
public bool DescribeAllParametersInCamelCase { get; set; }
public List Servers { get; set; }
@@ -63,8 +67,6 @@ public SwaggerGeneratorOptions()
public IList DocumentFilters { get; set; }
- public Func, Dictionary> SecuritySchemesSelector { get; set;}
-
private bool DefaultDocInclusionPredicate(string documentName, ApiDescription apiDescription)
{
return apiDescription.GroupName == null || apiDescription.GroupName == documentName;
@@ -106,26 +108,5 @@ private string DefaultSortKeySelector(ApiDescription apiDescription)
{
return TagsSelector(apiDescription).First();
}
-
- private Dictionary DefaultSecuritySchemeSelector(IEnumerable schemes)
- {
- Dictionary securitySchemes = new();
-#if (NET6_0_OR_GREATER)
- foreach (var scheme in schemes)
- {
- if (scheme.Name == "Bearer")
- {
- securitySchemes[scheme.Name] = new OpenApiSecurityScheme
- {
- Type = SecuritySchemeType.Http,
- Scheme = "bearer", // "bearer" refers to the header name here
- In = ParameterLocation.Header,
- BearerFormat = "Json Web Token"
- };
- }
- }
-#endif
- return securitySchemes;
- }
}
}
diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeAuthenticationSchemeProvider.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeAuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000..bbe01e472a
--- /dev/null
+++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/Fixtures/FakeAuthenticationSchemeProvider.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+
+namespace Swashbuckle.AspNetCore.SwaggerGen.Test
+{
+ public class FakeAuthenticationSchemeProvider : IAuthenticationSchemeProvider
+ {
+ private readonly IEnumerable _authenticationSchemes;
+
+ public FakeAuthenticationSchemeProvider(IEnumerable authenticationSchemes)
+ {
+ _authenticationSchemes = authenticationSchemes;
+ }
+
+ public void AddScheme(AuthenticationScheme scheme)
+ => throw new NotImplementedException();
+ public Task> GetAllSchemesAsync()
+ => Task.FromResult(_authenticationSchemes);
+
+ public Task GetDefaultAuthenticateSchemeAsync()
+ => Task.FromResult(_authenticationSchemes.First());
+
+ public Task GetDefaultChallengeSchemeAsync()
+ => Task.FromResult(_authenticationSchemes.First());
+
+ public Task GetDefaultForbidSchemeAsync()
+ => Task.FromResult(_authenticationSchemes.First());
+
+ public Task GetDefaultSignInSchemeAsync()
+ => Task.FromResult(_authenticationSchemes.First());
+
+ public Task GetDefaultSignOutSchemeAsync()
+ => Task.FromResult(_authenticationSchemes.First());
+
+ public Task> GetRequestHandlerSchemesAsync()
+ => throw new NotImplementedException();
+
+ public Task GetSchemeAsync(string name)
+ => Task.FromResult(_authenticationSchemes.First());
+
+ public void RemoveScheme(string name)
+ => throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs
index 53699c4aae..de8015624b 100644
--- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs
+++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs
@@ -13,8 +13,8 @@
using Swashbuckle.AspNetCore.TestSupport;
using Xunit;
using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Server.HttpSys;
+using Microsoft.AspNetCore.Authentication;
namespace Swashbuckle.AspNetCore.SwaggerGen.Test
{
@@ -1081,76 +1081,70 @@ public void GetSwagger_SupportsOption_SecuritySchemes()
var document = subject.GetSwagger("v1");
- Assert.Equal(new[] { "basic", "Bearer" }, document.Components.SecuritySchemes.Keys);
- }
-
- [Fact]
- public async Task GetSwagger_SupportsSecuritySchemesSelector()
- {
- var subject = Subject(
- apiDescriptions: new ApiDescription[] { },
- options: new SwaggerGeneratorOptions
- {
- SwaggerDocs = new Dictionary
- {
- ["v1"] = new OpenApiInfo { Version = "V1", Title = "Test API" }
- },
- SecuritySchemesSelector = (schemes) => new Dictionary
- {
- ["basic"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "basic" }
- }
- }
- );
-
- var document = await subject.GetSwaggerAsync("v1");
-
- // Overrides the default set of [basic, bearer] with just [basic]
Assert.Equal(new[] { "basic" }, document.Components.SecuritySchemes.Keys);
}
- [Fact]
- public async Task GetSwagger_DefaultSecuritySchemeSelectorAddsBearerByDefault()
+ [Theory]
+ [InlineData(false, new string[] { })]
+ [InlineData(true, new string[] { "Bearer" })]
+ public async Task GetSwagger_SupportsOption_InferSecuritySchemes(
+ bool inferSecuritySchemes,
+ string[] expectedSecuritySchemeNames)
+
{
var subject = Subject(
apiDescriptions: new ApiDescription[] { },
+ authenticationSchemes: new[] {
+ new AuthenticationScheme("Bearer", null, typeof(IAuthenticationHandler)),
+ new AuthenticationScheme("Cookies", null, typeof(IAuthenticationHandler))
+ },
options: new SwaggerGeneratorOptions
{
SwaggerDocs = new Dictionary
{
["v1"] = new OpenApiInfo { Version = "V1", Title = "Test API" }
},
+ InferSecuritySchemes = inferSecuritySchemes
}
);
var document = await subject.GetSwaggerAsync("v1");
- Assert.Equal(new[] { "Bearer" }, document.Components.SecuritySchemes.Keys);
+ Assert.Equal(expectedSecuritySchemeNames, document.Components.SecuritySchemes.Keys);
}
- [Fact]
- public async Task GetSwagger_DefaultSecuritySchemesSelectorDoesNotOverrideBearer()
+ [Theory]
+ [InlineData(false, new string[] { })]
+ [InlineData(true, new string[] { "Bearer", "Cookies" })]
+ public async Task GetSwagger_SupportsOption_SecuritySchemesSelector(
+ bool inferSecuritySchemes,
+ string[] expectedSecuritySchemeNames)
+
{
var subject = Subject(
apiDescriptions: new ApiDescription[] { },
+ authenticationSchemes: new[] {
+ new AuthenticationScheme("Bearer", null, typeof(IAuthenticationHandler)),
+ new AuthenticationScheme("Cookies", null, typeof(IAuthenticationHandler))
+ },
options: new SwaggerGeneratorOptions
{
SwaggerDocs = new Dictionary
{
["v1"] = new OpenApiInfo { Version = "V1", Title = "Test API" }
},
- SecuritySchemes = new Dictionary
- {
- ["Bearer"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, Scheme = "someSpecialOne" }
- }
+ InferSecuritySchemes = inferSecuritySchemes,
+ SecuritySchemesSelector = (authenticationSchemes) =>
+ authenticationSchemes
+ .ToDictionary(
+ (authScheme) => authScheme.Name,
+ (authScheme) => new OpenApiSecurityScheme())
}
);
var document = await subject.GetSwaggerAsync("v1");
- var securityScheme = Assert.Single(document.Components.SecuritySchemes);
- Assert.Equal("Bearer", securityScheme.Key);
- Assert.Equal(SecuritySchemeType.ApiKey, securityScheme.Value.Type);
- Assert.Equal("someSpecialOne", securityScheme.Value.Scheme);
+ Assert.Equal(expectedSecuritySchemeNames, document.Components.SecuritySchemes.Keys);
}
[Fact]
@@ -1283,13 +1277,16 @@ public void GetSwagger_SupportsOption_DocumentFilters()
Assert.Contains("ComplexType", document.Components.Schemas.Keys);
}
- private SwaggerGenerator Subject(IEnumerable apiDescriptions, SwaggerGeneratorOptions options = null)
+ private SwaggerGenerator Subject(
+ IEnumerable apiDescriptions,
+ SwaggerGeneratorOptions options = null,
+ IEnumerable authenticationSchemes = null)
{
return new SwaggerGenerator(
options ?? DefaultOptions,
new FakeApiDescriptionGroupCollectionProvider(apiDescriptions),
new SchemaGenerator(new SchemaGeneratorOptions(), new JsonSerializerDataContractResolver(new JsonSerializerOptions())),
- new TestAuthenticationSchemeProvider()
+ new FakeAuthenticationSchemeProvider(authenticationSchemes ?? Enumerable.Empty())
);
}
@@ -1301,41 +1298,4 @@ private SwaggerGenerator Subject(IEnumerable apiDescriptions, Sw
}
};
}
-
- class TestAuthenticationSchemeProvider : IAuthenticationSchemeProvider
- {
- private readonly IEnumerable _authenticationSchemes = new AuthenticationScheme[]
- {
- new AuthenticationScheme("Bearer", null, typeof(IAuthenticationHandler))
- };
-
- public void AddScheme(AuthenticationScheme scheme)
- => throw new NotImplementedException();
- public Task> GetAllSchemesAsync()
- => Task.FromResult(_authenticationSchemes);
-
- public Task GetDefaultAuthenticateSchemeAsync()
- => Task.FromResult(_authenticationSchemes.First());
-
- public Task GetDefaultChallengeSchemeAsync()
- => Task.FromResult(_authenticationSchemes.First());
-
- public Task GetDefaultForbidSchemeAsync()
- => Task.FromResult(_authenticationSchemes.First());
-
- public Task GetDefaultSignInSchemeAsync()
- => Task.FromResult(_authenticationSchemes.First());
-
- public Task GetDefaultSignOutSchemeAsync()
- => Task.FromResult(_authenticationSchemes.First());
-
- public Task> GetRequestHandlerSchemesAsync()
- => throw new NotImplementedException();
-
- public Task GetSchemeAsync(string name)
- => Task.FromResult(_authenticationSchemes.First());
-
- public void RemoveScheme(string name)
- => throw new NotImplementedException();
- }
}
\ No newline at end of file