Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OpenAPI] Cannot skip SecurityScheme for controllers with [AllowAnonymous] #61264

Open
1 task done
DavidNorena opened this issue Apr 1, 2025 · 2 comments
Open
1 task done
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi

Comments

@DavidNorena
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-9.0

documentation shows you can do something like this

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            // Add the security scheme at the document level
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;

            // Apply it as a requirement for all operations
            foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
            {
                operation.Value.Security.Add(new OpenApiSecurityRequirement
                {
                    [new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] = Array.Empty<string>()
                });
            }
        }
    }
}

Which applies the security schema for all the endpoints, but it doesnt show a way to skip the endpoints or controllers that have the AllowAnonymousAttribute applied

for now the only workaround I have found is by using Tags, but it is a little hacky.

internal sealed class BearerSecuritySchemeTransformer(
    IAuthenticationSchemeProvider authenticationSchemeProvider
) : IOpenApiDocumentTransformer
{
    private const string SecuritySchemeName = "Bearer";
    private const string AnonymousTagName = "Anonymous";

    public async Task TransformAsync(
        OpenApiDocument document,
        OpenApiDocumentTransformerContext context,
        CancellationToken cancellationToken
    )
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        bool hasBearerScheme = authenticationSchemes.Any(authScheme => authScheme.Name == SecuritySchemeName);

        if (hasBearerScheme)
        {
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();

            // Only add if it doesn't exist - avoid conflicts if multiple transformers run
            if (!document.Components.SecuritySchemes.ContainsKey(SecuritySchemeName))
            {
                document.Components.SecuritySchemes.Add(SecuritySchemeName, new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer",
                    BearerFormat = "JWT",
                    Name = "Authorization",
                    In = ParameterLocation.Header,
                });
            }
        }

        // Remove Anonymous tag from the tags collection if it exists
        var anonymousTag = document.Tags.FirstOrDefault(t => t.Name == AnonymousTagName);
        if (anonymousTag != null)
        {
            document.Tags.Remove(anonymousTag);
        }

        foreach (var path in document.Paths.Values)
        {
            foreach (var operation in path.Operations)
            {
                // Check if operation has Anonymous tag
                var anonymousTagInOperation = operation.Value.Tags.FirstOrDefault(t => t.Name == AnonymousTagName);

                if (anonymousTagInOperation != null)
                {
                    // Remove Anonymous tag from the operation
                    operation.Value.Tags.Remove(anonymousTagInOperation);

                    // Skip adding security requirement
                    continue;
                }

                // Add security requirement for non-anonymous operations
                operation.Value.Security.Add(new OpenApiSecurityRequirement
                {
                    [new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Id = SecuritySchemeName,
                            Type = ReferenceType.SecurityScheme
                        }
                    }] = Array.Empty<string>()
                });
            }
        }
    }
}

Expected Behavior

Please show an example on how to skip security requirements for controllers / endpoints that have the AllowAnonymousAttribute applied

Thanks.

Steps To Reproduce

The steps to repro the issue are at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-9.0#use-document-transformers

Exceptions (if any)

No response

.NET Version

9.0.104

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Apr 1, 2025
@martincostello martincostello added feature-openapi area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc and removed needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically labels Apr 1, 2025
@xC0dex
Copy link
Contributor

xC0dex commented Apr 1, 2025

Hey @DavidNorena,

If you like to skip certain endpoints, I'd recommend using an operation transformer instead of a document transformer. In the OpenApiOperationTransformerContext you have access to all attributes of the endpoint. You could check out this code. It shows and extension method HasAuthorizationAsync() which is used here. This method checks for existing attributes to determine if a security requirement should be added to an operation.

@DavidNorena
Copy link
Author

@xC0dex thanks a lot for your comment, do you think this should be documented somehow ?

Seems not to be a bug in OpenAPI package but a lack of documentation

I tested your implementation and works very well thanks a lot for your reply

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-openapi
Projects
None yet
Development

No branches or pull requests

3 participants