Skip to content

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

@DavidNorena

Description

@DavidNorena

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    DocsThis issue tracks updating documentationarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etcfeature-openapigood first issueGood for newcomers.help wantedUp for grabs. We would accept a PR to help resolve this issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions