-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add authentication support in Swagger for .NET 9.0+
Introduce new functionality for adding authentication support in Swagger for .NET 9.0 or greater. This includes configuring and displaying the Authorize button in the Swagger UI for JWT Bearer, API Key, and Basic Authentication. Encapsulate changes within `#if NET9_0_OR_GREATER` preprocessor directives to ensure compatibility with .NET 9.0+. Add new using directives for necessary namespaces. Introduce `SwaggerExtensions` class in `OpenApiExtensions.cs` with `AddSimpleAuthentication` methods for configuring Swagger authentication. Add `AuthenticationDocumentTransformer` class in `AuthenticationDocumentTransformer.cs` to transform OpenAPI documents with security definitions and requirements. Add `AuthenticationOperationTransformer` class in `AuthenticationOperationTransformer.cs` to automatically add 401 and 403 responses to authorized operations. Introduce `Helpers` class in `Helpers.cs` with utility methods for creating security requirements and responses. Ensure Swagger UI supports various authentication schemes for easier testing of authenticated endpoints.
- Loading branch information
1 parent
42f9978
commit 9ba1a77
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
109 changes: 109 additions & 0 deletions
109
src/SimpleAuthentication/OpenApi/AuthenticationDocumentTransformer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#if NET9_0_OR_GREATER | ||
|
||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.AspNetCore.OpenApi; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Net.Http.Headers; | ||
using Microsoft.OpenApi.Models; | ||
using SimpleAuthentication.ApiKey; | ||
using SimpleAuthentication.BasicAuthentication; | ||
using SimpleAuthentication.JwtBearer; | ||
|
||
namespace SimpleAuthentication.OpenApi; | ||
|
||
internal class AuthenticationDocumentTransformer(IConfiguration configuration, string sectionName, params IEnumerable<OpenApiSecurityRequirement> additionalSecurityRequirements) : IOpenApiDocumentTransformer | ||
{ | ||
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) | ||
{ | ||
// Adds a security definition for each authentication method that has been configured. | ||
CheckAddJwtBearer(document, configuration.GetSection($"{sectionName}:JwtBearer")); | ||
CheckAddApiKey(document, configuration.GetSection($"{sectionName}:ApiKey")); | ||
CheckAddBasicAuthentication(document, configuration.GetSection($"{sectionName}:Basic")); | ||
|
||
if (additionalSecurityRequirements?.Any() ?? false) | ||
{ | ||
// Adds all the other security requirements that have been specified. | ||
foreach (var securityRequirement in additionalSecurityRequirements) | ||
{ | ||
AddSecurityRequirement(document, securityRequirement); | ||
} | ||
} | ||
|
||
return Task.CompletedTask; | ||
|
||
static void CheckAddJwtBearer(OpenApiDocument document, IConfigurationSection section) | ||
{ | ||
var settings = section.Get<JwtBearerSettings>(); | ||
if (settings is null) | ||
{ | ||
return; | ||
} | ||
|
||
AddSecurityScheme(document, settings.SchemeName, SecuritySchemeType.Http, JwtBearerDefaults.AuthenticationScheme, ParameterLocation.Header, HeaderNames.Authorization, "Insert the Bearer Token"); | ||
AddSecurityRequirement(document, settings.SchemeName); | ||
} | ||
|
||
static void CheckAddApiKey(OpenApiDocument document, IConfigurationSection section) | ||
{ | ||
var settings = section.Get<ApiKeySettings>(); | ||
if (settings is null) | ||
{ | ||
return; | ||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(settings.HeaderName)) | ||
{ | ||
AddSecurityScheme(document, $"{settings.SchemeName} in Header", SecuritySchemeType.ApiKey, null, ParameterLocation.Header, settings.HeaderName, "Insert the API Key"); | ||
AddSecurityRequirement(document, $"{settings.SchemeName} in Header"); | ||
} | ||
|
||
if (!string.IsNullOrWhiteSpace(settings.QueryStringKey)) | ||
{ | ||
AddSecurityScheme(document, $"{settings.SchemeName} in Query String", SecuritySchemeType.ApiKey, null, ParameterLocation.Query, settings.QueryStringKey, "Insert the API Key"); | ||
AddSecurityRequirement(document, $"{settings.SchemeName} in Query String"); | ||
} | ||
} | ||
|
||
static void CheckAddBasicAuthentication(OpenApiDocument document, IConfigurationSection section) | ||
{ | ||
var settings = section.Get<BasicAuthenticationSettings>(); | ||
if (settings is null) | ||
{ | ||
return; | ||
} | ||
|
||
AddSecurityScheme(document, settings.SchemeName, SecuritySchemeType.Http, BasicAuthenticationDefaults.AuthenticationScheme, ParameterLocation.Header, HeaderNames.Authorization, "Insert user name and password"); | ||
AddSecurityRequirement(document, settings.SchemeName); | ||
} | ||
} | ||
|
||
private static void AddSecurityScheme(OpenApiDocument document, string name, SecuritySchemeType securitySchemeType, string? scheme, ParameterLocation location, string parameterName, string description) | ||
{ | ||
document.Components ??= new(); | ||
document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>(); | ||
|
||
document.Components.SecuritySchemes.Add(name, new() | ||
{ | ||
In = location, | ||
Name = parameterName, | ||
Description = description, | ||
Type = securitySchemeType, | ||
Scheme = scheme, | ||
//Reference = new() | ||
//{ | ||
// Id = name, | ||
// Type = ReferenceType.SecurityScheme | ||
//} | ||
}); | ||
} | ||
private static void AddSecurityRequirement(OpenApiDocument document, string name) | ||
=> AddSecurityRequirement(document, Helpers.CreateSecurityRequirement(name)); | ||
|
||
private static void AddSecurityRequirement(OpenApiDocument document, OpenApiSecurityRequirement requirement) | ||
{ | ||
document.SecurityRequirements ??= []; | ||
document.SecurityRequirements.Add(requirement); | ||
} | ||
} | ||
|
||
#endif |
42 changes: 42 additions & 0 deletions
42
src/SimpleAuthentication/OpenApi/AuthenticationOperationTransformer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#if NET9_0_OR_GREATER | ||
|
||
using System.Net; | ||
using System.Runtime.CompilerServices; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Authorization.Infrastructure; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.OpenApi; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.Options; | ||
using Microsoft.Net.Http.Headers; | ||
using Microsoft.OpenApi.Models; | ||
using SimpleAuthentication.ApiKey; | ||
using SimpleAuthentication.BasicAuthentication; | ||
using SimpleAuthentication.JwtBearer; | ||
|
||
namespace SimpleAuthentication.OpenApi; | ||
|
||
internal class AuthenticationOperationTransformer(IAuthorizationPolicyProvider authorizationPolicyProvider) : IOpenApiOperationTransformer | ||
{ | ||
public async Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) | ||
{ | ||
// If the method requires authorization, automatically add 401 and 403 response (if not explicitly specified). | ||
var fallbackPolicy = await authorizationPolicyProvider.GetFallbackPolicyAsync(); | ||
var requireAuthenticatedUser = fallbackPolicy?.Requirements.Any(r => r is DenyAnonymousAuthorizationRequirement) ?? false; | ||
|
||
var endpointMetadata = context.Description.ActionDescriptor.EndpointMetadata; | ||
|
||
var requireAuthorization = endpointMetadata.Any(m => m is AuthorizeAttribute); | ||
var allowAnonymous = endpointMetadata.Any(m => m is AllowAnonymousAttribute); | ||
|
||
if ((requireAuthenticatedUser || requireAuthorization) && !allowAnonymous) | ||
{ | ||
operation.Responses.TryAdd(StatusCodes.Status401Unauthorized.ToString(), Helpers.CreateResponse(HttpStatusCode.Unauthorized.ToString())); | ||
operation.Responses.TryAdd(StatusCodes.Status403Forbidden.ToString(), Helpers.CreateResponse(HttpStatusCode.Forbidden.ToString())); | ||
} | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
#if NET9_0_OR_GREATER | ||
|
||
using System.Net.Mime; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Microsoft.OpenApi.Models; | ||
|
||
namespace SimpleAuthentication.OpenApi; | ||
|
||
internal static class Helpers | ||
{ | ||
public static OpenApiSecurityRequirement CreateSecurityRequirement(string name) | ||
{ | ||
var requirement = new OpenApiSecurityRequirement() | ||
{ | ||
{ | ||
new() | ||
{ | ||
Reference = new() | ||
{ | ||
Type = ReferenceType.SecurityScheme, | ||
Id = name | ||
} | ||
}, | ||
[] | ||
} | ||
}; | ||
|
||
return requirement; | ||
} | ||
|
||
public static OpenApiResponse CreateResponse(string description) | ||
=> new() | ||
{ | ||
Description = description, | ||
Content = new Dictionary<string, OpenApiMediaType> | ||
{ | ||
[MediaTypeNames.Application.ProblemJson] = new() | ||
{ | ||
Schema = new() | ||
{ | ||
Reference = new() | ||
{ | ||
Type = ReferenceType.Schema, | ||
Id = nameof(ProblemDetails) | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#if NET9_0_OR_GREATER | ||
|
||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.AspNetCore.OpenApi; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Net.Http.Headers; | ||
using Microsoft.OpenApi.Models; | ||
using SimpleAuthentication.ApiKey; | ||
using SimpleAuthentication.BasicAuthentication; | ||
using SimpleAuthentication.JwtBearer; | ||
using SimpleAuthentication.OpenApi; | ||
|
||
namespace SimpleAuthentication; | ||
|
||
/// <summary> | ||
/// Provides extension methods for adding authentication support in Swagger. | ||
/// </summary> | ||
public static class SwaggerExtensions | ||
{ | ||
/// <summary> | ||
/// Adds authentication support in Swagger, enabling the Authorize button in the Swagger UI, reading configuration from the specified <see cref="IConfiguration"/> source. | ||
/// </summary> | ||
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration"/> being bound.</param> | ||
/// <param name="sectionName">The name of the configuration section that holds authentication settings (default: Authentication).</param> | ||
/// <seealso cref="OpenApiOptions"/> | ||
/// <seealso cref="IConfiguration"/> | ||
public static void AddSimpleAuthentication(this OpenApiOptions options, IConfiguration configuration, string sectionName = "Authentication") | ||
=> options.AddSimpleAuthentication(configuration, sectionName, Array.Empty<OpenApiSecurityRequirement>()); | ||
|
||
/// <summary> | ||
/// Adds authentication support in Swagger, enabling the Authorize button in the Swagger UI, reading configuration from a section named <strong>Authentication</strong> in <see cref="IConfiguration"/> source. | ||
/// </summary> | ||
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration"/> being bound.</param> | ||
/// <param name="additionalSecurityDefinitionNames">The name of additional security definitions that have been defined in Swagger.</param> | ||
/// <seealso cref="OpenApiOptions"/> | ||
/// <seealso cref="IConfiguration"/> | ||
public static void AddSimpleAuthentication(this OpenApiOptions options, IConfiguration configuration, params IEnumerable<string> additionalSecurityDefinitionNames) | ||
=> options.AddSimpleAuthentication(configuration, "Authentication", additionalSecurityDefinitionNames); | ||
|
||
/// <summary> | ||
/// Adds authentication support in Swagger, enabling the Authorize button in the Swagger UI, reading configuration from the specified <see cref="IConfiguration"/> source. | ||
/// </summary> | ||
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration"/> being bound.</param> | ||
/// <param name="sectionName">The name of the configuration section that holds authentication settings (default: Authentication).</param> | ||
/// <param name="additionalSecurityDefinitionNames">The name of additional security definitions that have been defined in Swagger.</param> | ||
/// <seealso cref="OpenApiOptions"/> | ||
/// <seealso cref="IConfiguration"/> | ||
public static void AddSimpleAuthentication(this OpenApiOptions options, IConfiguration configuration, string sectionName, params IEnumerable<string> additionalSecurityDefinitionNames) | ||
{ | ||
var securityRequirements = additionalSecurityDefinitionNames?.Select(Helpers.CreateSecurityRequirement).ToArray(); | ||
options.AddSimpleAuthentication(configuration, sectionName, securityRequirements ?? []); | ||
} | ||
|
||
/// <summary> | ||
/// Adds authentication support in Swagger, enabling the Authorize button in the Swagger UI, reading configuration from the specified <see cref="IConfiguration"/> source. | ||
/// </summary> | ||
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration"/> being bound.</param> | ||
/// <param name="securityRequirements">Additional security requirements to be added to Swagger definition.</param> | ||
/// <seealso cref="OpenApiOptions"/> | ||
/// <seealso cref="IConfiguration"/> | ||
public static void AddSimpleAuthentication(this OpenApiOptions options, IConfiguration configuration, params IEnumerable<OpenApiSecurityRequirement> securityRequirements) | ||
=> options.AddSimpleAuthentication(configuration, "Authentication", securityRequirements); | ||
|
||
/// <summary> | ||
/// Adds authentication support in Swagger, enabling the Authorize button in the Swagger UI, reading configuration from the specified <see cref="IConfiguration"/> source. | ||
/// </summary> | ||
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param> | ||
/// <param name="configuration">The <see cref="IConfiguration"/> being bound.</param> | ||
/// <param name="sectionName">The name of the configuration section that holds authentication settings (default: Authentication).</param> | ||
/// <param name="additionalSecurityRequirements">Additional security requirements to be added to Swagger definition.</param> | ||
/// <seealso cref="OpenApiOptions"/> | ||
/// <seealso cref="IConfiguration"/> | ||
public static void AddSimpleAuthentication(this OpenApiOptions options, IConfiguration configuration, string sectionName, params IEnumerable<OpenApiSecurityRequirement> additionalSecurityRequirements) | ||
{ | ||
ArgumentNullException.ThrowIfNull(options); | ||
ArgumentNullException.ThrowIfNull(configuration); | ||
ArgumentNullException.ThrowIfNull(sectionName); | ||
|
||
options.AddDocumentTransformer(new AuthenticationDocumentTransformer(configuration, sectionName, additionalSecurityRequirements)); | ||
options.AddOperationTransformer<AuthenticationOperationTransformer>(); | ||
} | ||
} | ||
|
||
#endif |