Skip to content

Commit

Permalink
Refactor OpenAPI/Swagger auth integration
Browse files Browse the repository at this point in the history
Refactored and added new functionality for OpenAPI and Swagger integration related to authentication in a .NET project. Removed `Helpers` class and moved its functionality to a new `OpenApiHelpers` class. Updated `AuthenticationOperationFilter` and `AuthenticationOperationTransformer` to use `OpenApiHelpers`. Updated `SwaggerExtensions` and `OpenApiExtensions` to support OAuth2 authentication, including new methods for adding OAuth2 schemes and security requirements. Introduced `OAuth2AuthenticationDocumentTransformer` for transforming OpenAPI documents to include OAuth2. Ensured compatibility with .NET 9.0+ using conditional compilation. Defined `OpenApiHelpers` in both `SimpleAuthentication.Swagger` and `SimpleAuthentication.OpenApi` namespaces for different contexts.

#127 #128
  • Loading branch information
marcominerva committed Jan 8, 2025
1 parent 5f6ab87 commit 3fd85e6
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public async Task ApplyAsync(OpenApiOperation operation, OperationFilterContext

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()));
operation.Responses.TryAdd(StatusCodes.Status401Unauthorized.ToString(), OpenApiHelpers.CreateResponse(HttpStatusCode.Unauthorized.ToString()));
operation.Responses.TryAdd(StatusCodes.Status403Forbidden.ToString(), OpenApiHelpers.CreateResponse(HttpStatusCode.Forbidden.ToString()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace SimpleAuthentication.Swagger;

internal static class Helpers
internal static class OpenApiHelpers
{
public static OpenApiSecurityRequirement CreateSecurityRequirement(string name)
=> new()
Expand Down
57 changes: 54 additions & 3 deletions src/SimpleAuthentication.Swashbuckle/SwaggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void AddSimpleAuthentication(this SwaggerGenOptions options, IConf
/// <seealso cref="IConfiguration"/>
public static void AddSimpleAuthentication(this SwaggerGenOptions options, IConfiguration configuration, string sectionName, IEnumerable<string>? additionalSecurityDefinitionNames)
{
var securityRequirements = additionalSecurityDefinitionNames?.Select(Helpers.CreateSecurityRequirement).ToArray();
var securityRequirements = additionalSecurityDefinitionNames?.Select(OpenApiHelpers.CreateSecurityRequirement).ToArray();
options.AddSimpleAuthentication(configuration, sectionName, securityRequirements ?? []);
}

Expand Down Expand Up @@ -150,8 +150,59 @@ static void AddSecurityDefinition(SwaggerGenOptions options, string name, Securi
Type = securitySchemeType,
Scheme = scheme
});
}

/// <summary>
/// Adds OAuth2 authentication support in Swagger, for example for example to integrate with Microsoft.Identity.Web.
/// </summary>
/// <param name="options">The <see cref="SwaggerGenOptions"/> to add configuration to.</param>
/// <param name="name">The name of the OAuth2 authentication scheme to add.</param>
/// <param name="authorizationUrl">The Authorization Url for OAuth2 authorization.</param>
/// <param name="tokenUrl">The Token Url for OAuth2 authorization.</param>
/// <param name="scopes">The list of scopes.</param>
/// <seealso cref="SwaggerGenOptions"/>
public static void AddOAuth2Authentication(this SwaggerGenOptions options, string name, string authorizationUrl, string tokenUrl, IDictionary<string, string> scopes)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentException.ThrowIfNullOrWhiteSpace(authorizationUrl);
ArgumentException.ThrowIfNullOrWhiteSpace(tokenUrl);
ArgumentNullException.ThrowIfNull(scopes);

static void AddSecurityRequirement(SwaggerGenOptions options, string name)
=> options.AddSecurityRequirement(Helpers.CreateSecurityRequirement(name));
options.AddOAuth2Authentication(name, new()
{
AuthorizationUrl = new(authorizationUrl),
TokenUrl = new(tokenUrl),
Scopes = scopes
});
}

/// <summary>
/// Adds OAuth2 authentication support in Open API, for example for example to integrate with Microsoft.Identity.Web.
/// </summary>
/// <param name="options">The <see cref="SwaggerGenOptions"/> to add configuration to.</param>
/// <param name="name">The name of the OAuth2 authentication scheme to add.</param>
/// <param name="authFlow">The <see cref="OpenApiOAuthFlow">object</see> that describes the authorization flow.</param>
/// <seealso cref="SwaggerGenOptions"/>
/// <seealso cref="OpenApiOAuthFlow"/>
public static void AddOAuth2Authentication(this SwaggerGenOptions options, string name, OpenApiOAuthFlow authFlow)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(authFlow);

options.AddSecurityDefinition(name, new()
{
Type = SecuritySchemeType.OAuth2,
Flows = new()
{
Implicit = authFlow
}
});

AddSecurityRequirement(options, name);
}

private static void AddSecurityRequirement(SwaggerGenOptions options, string name)
=> options.AddSecurityRequirement(OpenApiHelpers.CreateSecurityRequirement(name));
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,12 @@ private static void AddSecurityScheme(OpenApiDocument document, string name, Sec
Name = parameterName,
Description = description,
Type = securitySchemeType,
Scheme = scheme,
//Reference = new()
//{
// Id = name,
// Type = ReferenceType.SecurityScheme
//}
Scheme = scheme
});
}
private static void AddSecurityRequirement(OpenApiDocument document, string name)
=> AddSecurityRequirement(document, Helpers.CreateSecurityRequirement(name));

internal static void AddSecurityRequirement(OpenApiDocument document, string name)
=> AddSecurityRequirement(document, OpenApiHelpers.CreateSecurityRequirement(name));

private static void AddSecurityRequirement(OpenApiDocument document, OpenApiSecurityRequirement requirement)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public async Task TransformAsync(OpenApiOperation operation, OpenApiOperationTra

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()));
operation.Responses.TryAdd(StatusCodes.Status401Unauthorized.ToString(), OpenApiHelpers.CreateResponse(HttpStatusCode.Unauthorized.ToString()));
operation.Responses.TryAdd(StatusCodes.Status403Forbidden.ToString(), OpenApiHelpers.CreateResponse(HttpStatusCode.Forbidden.ToString()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#if NET9_0_OR_GREATER

using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

namespace SimpleAuthentication.OpenApi;

internal class OAuth2AuthenticationDocumentTransformer(string name, OpenApiOAuthFlow authFlow) : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
{
document.Components ??= new();
document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();

document.Components.SecuritySchemes.Add(name, new()
{
Type = SecuritySchemeType.OAuth2,
Flows = new()
{
Implicit = authFlow
}
});

AuthenticationDocumentTransformer.AddSecurityRequirement(document, name);

return Task.CompletedTask;
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace SimpleAuthentication.OpenApi;

internal static class Helpers
internal static class OpenApiHelpers
{
public static OpenApiSecurityRequirement CreateSecurityRequirement(string name)
=> new()
Expand Down
49 changes: 42 additions & 7 deletions src/SimpleAuthentication/OpenApiExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
#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;
Expand Down Expand Up @@ -51,7 +45,7 @@ public static void AddSimpleAuthentication(this OpenApiOptions options, IConfigu
/// <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();
var securityRequirements = additionalSecurityDefinitionNames?.Select(OpenApiHelpers.CreateSecurityRequirement).ToArray();
options.AddSimpleAuthentication(configuration, sectionName, securityRequirements ?? []);
}

Expand Down Expand Up @@ -84,6 +78,47 @@ public static void AddSimpleAuthentication(this OpenApiOptions options, IConfigu
options.AddDocumentTransformer(new AuthenticationDocumentTransformer(configuration, sectionName, additionalSecurityRequirements));
options.AddOperationTransformer<AuthenticationOperationTransformer>();
}

/// <summary>
/// Adds OAuth2 custom authentication support in Open API, for example to integrate with Microsoft.Identity.Web.
/// </summary>
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param>
/// <param name="name">The name of the OAuth2 authentication scheme to add.</param>
/// <param name="authorizationUrl">The Authorization Url for OAuth2 authorization.</param>
/// <param name="tokenUrl">The Token Url for OAuth2 authorization.</param>
/// <param name="scopes">The list of scopes.</param>
/// <seealso cref="OpenApiOptions"/>
public static void AddOAuth2Authentication(this OpenApiOptions options, string name, string authorizationUrl, string tokenUrl, IDictionary<string, string> scopes)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentException.ThrowIfNullOrWhiteSpace(authorizationUrl);
ArgumentException.ThrowIfNullOrWhiteSpace(tokenUrl);
ArgumentNullException.ThrowIfNull(scopes);

options.AddOAuth2Authentication(name, new()
{
AuthorizationUrl = new(authorizationUrl),
TokenUrl = new(tokenUrl),
Scopes = scopes
});
}

/// <summary>
/// Adds OAuth2 custom authentication support in Open API, for example to integrate with Microsoft.Identity.Web.
/// </summary>
/// <param name="options">The <see cref="OpenApiOptions"/> to add configuration to.</param>
/// <param name="name">The name of the OAuth2 authentication scheme to add.</param>
/// <param name="authFlow">The <see cref="OpenApiOAuthFlow">object</see> that describes the authorization flow.</param>
/// <seealso cref="OpenApiOptions"/>
/// <seealso cref="OpenApiOAuthFlow"/>
public static void AddOAuth2Authentication(this OpenApiOptions options, string name, OpenApiOAuthFlow authFlow)
{
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(authFlow);

options.AddDocumentTransformer(new OAuth2AuthenticationDocumentTransformer(name, authFlow));
}
}

#endif

0 comments on commit 3fd85e6

Please sign in to comment.