DEV Community

Virendra Vyas
Virendra Vyas

Posted on

Swagger UI Is Showing the Wrong Auth Requirement on Every Endpoint — Here's the Fix

If you've wired up Swashbuckle with AddSecurityDefinition and AddSecurityRequirement following the standard tutorial, you've probably hit this: every single endpoint in Swagger UI shows the padlock icon and demands the same auth scheme - even endpoints that don't need auth at all, or that need a different scheme entirely. This gets worse fast once you're running more than one JWT scheme in the same API.

Why this happens

The common tutorial snippet looks like this:

builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

AddSecurityRequirement at this level applies globally - to every operation in the generated spec, regardless of whether the underlying endpoint actually requires that scheme, or requires a different one, or requires no auth at all. Swashbuckle has no way to know your intent unless you tell it per-operation.

The fix: an IOperationFilter

The right tool here is an IOperationFilter that inspects each operation's actual authorization metadata and applies the security requirement only where it's genuinely needed:

public class SecurityRequirementsOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var hasAuthorize = context.MethodInfo.DeclaringType!
            .GetCustomAttributes(true)
            .OfType<AuthorizeAttribute>()
            .Any() ||
            context.MethodInfo
            .GetCustomAttributes(true)
            .OfType<AuthorizeAttribute>()
            .Any();

        var hasAnonymous = context.MethodInfo
            .GetCustomAttributes(true)
            .OfType<AllowAnonymousAttribute>()
            .Any();

        if (!hasAuthorize || hasAnonymous)
        {
            return; // no auth required, no padlock
        }

        var authorizeAttributes = context.MethodInfo.DeclaringType!
            .GetCustomAttributes(true)
            .OfType<AuthorizeAttribute>()
            .Concat(context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>());

        var schemeNames = authorizeAttributes
            .Select(a => a.AuthenticationSchemes)
            .Where(s => !string.IsNullOrEmpty(s))
            .SelectMany(s => s!.Split(','))
            .Distinct()
            .DefaultIfEmpty("Bearer") // fallback to your default scheme name
            .ToList();

        operation.Security = new List<OpenApiSecurityRequirement>
        {
            new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = schemeNames.First() // scheme relevant to this operation
                        }
                    },
                    Array.Empty<string>()
                }
            }
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Register it alongside your security definitions:

builder.Services.AddSwaggerGen(options =>
{
    // ... AddSecurityDefinition calls for each scheme (e.g. "MainScheme", "UserTokenScheme")

    options.OperationFilter<SecurityRequirementsOperationFilter>();
});
Enter fullscreen mode Exit fullscreen mode

Now the generated spec - and Swagger UI's padlock icons - reflect reality: endpoints with [AllowAnonymous] show no padlock, endpoints with [Authorize] (no scheme specified) get your default, and endpoints with [Authorize(AuthenticationSchemes = "UserTokenScheme")] correctly show that they need the other token, not the main one.

Why this matters more with multiple schemes

If you're only running a single JWT scheme, the blanket AddSecurityRequirement approach is merely imprecise — annoying, but not actively misleading. Once you're running two schemes side by side (see my earlier post on dual JWT Bearer schemes), the global approach becomes actively wrong: it tells every consumer of your Swagger UI to authenticate with the wrong token on some endpoints, which is exactly the kind of thing that sends other developers down the wrong debugging path entirely.

Checklist

  • [ ] Does Swagger UI show a padlock only on endpoints that actually require auth?
  • [ ] If you have [AllowAnonymous] endpoints, do they correctly show no lock?
  • [ ] If you're running multiple schemes, does each protected endpoint show the correct scheme, not just "a" scheme?
  • [ ] Have you tested the Authorize button end-to-end — does the token you enter actually get attached to requests for the endpoint you're testing?

This is a small amount of extra setup for a much more honest Swagger UI — and it stops other developers (or future you) from trusting a spec that's quietly lying about what each endpoint needs.

Top comments (0)