6

I have the following IOperationFilter class, which implements the authentication headers required for some endpoints in my API application:

public class AuthenticationHeadersFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (operation.Parameters == null)
            operation.Parameters = new List<OpenApiParameter>();

        operation.Parameters.Add(new OpenApiParameter
            {
                Name = "AccountName",
                In = ParameterLocation.Header,
                Required = true
            });

        operation.Parameters.Add(new OpenApiParameter
            {
                Name = "ApiKey",
                In = ParameterLocation.Header,
                Required = true
            });
    }
}

The above is added to my application's Swagger UI through the following in the ConfigureServices method of Startup.cs:

services.AddSwaggerGen(c =>
{
    // ...
    // other Swagger configurations
    // ... 
    c.OperationFilter<AuthenticationHeadersFilter>();
});

This works well, however I now also have some endpoints which I'd like to be documented/displayed by Swagger, but should be completely publicly accessible without requiring the user to supply the AccountName and ApiKey as headers in their API request. How can I accomplish this?

I found this Stack Overflow answer, but not sure if it can be adapted for my purpose here. I wasn't able to find any useful documentation regarding the OperationFilterContext class.

Any help is greatly appreciated.

Jethro Cao
  • 968
  • 1
  • 9
  • 19

2 Answers2

9

There is SwaggerOperationFilter attribute specifically for this case.

You need to install package Swashbuckle.AspNetCore.Annotations and enable it:

// Startup.cs
services.AddSwaggerGen(c =>
{
    ...
    c.EnableAnnotations();
};

Then you can use it like [SwaggerOperationFilter(typeof(YourFilterClass))] instead of c.OperationFilter<YourFilterClass>();

Ivan Sveshnikov
  • 319
  • 4
  • 10
6

Possible solution could be to have an attribute like this

using System;

namespace some.namespace
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
    public class SkipAuthenticationHeadersAttribute : Attribute
    {
    }
}

and use it in swagger AuthenticationHeadersFilter like this:

using System.Linq;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace some.namespace
{
    /// <summary>
    /// https://alexdunn.org/2018/06/29/adding-a-required-http-header-to-your-swagger-ui-with-swashbuckle/.
    /// </summary>
    public class AuthenticationHeadersFilter : IOperationFilter
    {
        /// <summary>
        /// Swagger: Creates new required http header.
        /// </summary>
        /// <param name="operation"> Open Api Operation.</param>
        /// <param name="context">Operation Filter Context.</param>
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            var globalAttributes = context.ApiDescription.ActionDescriptor.FilterDescriptors.Select(p => p.Filter);
            var controllerAttributes = context.MethodInfo?.DeclaringType?.GetCustomAttributes(true);
            var methodAttributes = context.MethodInfo?.GetCustomAttributes(true);
            var produceAttributes = globalAttributes
                .Union(controllerAttributes ?? throw new InvalidOperationException())
                .Union(methodAttributes)
                .OfType<SkipAuthenticationHeadersAttribute>()
                .ToList();

            if (produceAttributes.Count != 0)
            {
                return;
            }

            if (operation.Parameters == null)
            {
                operation.Parameters = new List<OpenApiParameter>();
            }

            operation.Parameters.Add(new OpenApiParameter
            {
               Name = "AccountName",
               In = ParameterLocation.Header,
               Required = true
            });

            operation.Parameters.Add(new OpenApiParameter
            {
               Name = "ApiKey",
               In = ParameterLocation.Header,
               Required = true
            });
        }
    }
}

So when you add the attribute to a controller action, the action is filtered out.

Aspram
  • 585
  • 5
  • 16