Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP Net Core 2.2 add locker icon only to methods that require authorization - Swagger UI

Versions:

  • ASP Net Core Web API - 2.2
  • Swashbuckle.AspNetCore - 4.0.1

What I currently have?

I have implemented swagger in my Web API project. And I am using JWT authorization with [Authorize] attribute on the methods that require it.

So I wanted an easy way to be able to send requests that require authorization. In my ConfigureServices class, I've added the following logic.

services.AddSwaggerGen(c =>
{

    // Other swagger options

    c.AddSecurityDefinition("Bearer", new ApiKeyScheme
    {
        In = "header",
        Description = "Please enter into field the word 'Bearer' following by space and your JWT token",
        Name = "Authorization",
        Type = "apiKey"
    });
    c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
    {
        { "Bearer", Enumerable.Empty<string>() },
    });

    // Other swagger options
});

What this does is the following:
It adds one new button in swagger - Authorize.

enter image description here

The problem is, it also adds an "open" locker icon, next to every method. Even though, some of them require authorization.

enter image description here

And when I authorize successfully using the Authorize button (It basically adds header Authorization to each request), I receive a "closed" locker on all of them. enter image description here

I know this is probably desired functionality to indicate that an Authorization token will be sent via the request. I want a way to show which methods require authorization and which don't.

What do I want?

For instance, the "open" locker for anonymous methods and "closed" locker for methods that have [Authorize] attribute on them.

It could be an additional icon, next to this or to modify the behaviour of this one, no problem. How can I achieve this?

Possible solution?

I believe a possible solution is to make an OperationFilter and go through all methods and attach "something" only to those that have [Authorize] attribute on them. Is this the best solution? If so, how would you implement it?

like image 648
G.Dimov Avatar asked Aug 06 '19 08:08

G.Dimov


3 Answers

Since it went more than a month since I asked this one. Here is how I did it.

I deleted the following code from Startup.cs:

c.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
    In = "header",
    Description = "Please enter into field the word 'Bearer' following by space and your JWT token",
    Name = "Authorization",
    Type = "apiKey"
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
{
    { "Bearer", Enumerable.Empty<string>() },
});

And I added the following one:

c.OperationFilter<AddAuthHeaderOperationFilter>();

And of course the AddAuthHeaderOperationFilter.cs:

    public class AddAuthHeaderOperationFilter : IOperationFilter
    {
        private readonly IHttpContextAccessor httpContextAccessor;

        public AddAuthHeaderOperationFilter(IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor;
        }

        public void Apply(Operation operation, OperationFilterContext context)
        {
            var filterDescriptor = context.ApiDescription.ActionDescriptor.FilterDescriptors;
            var isAuthorized = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
            var allowAnonymous = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);

            if (isAuthorized && !allowAnonymous)
            {
                if (operation.Parameters == null)
                    operation.Parameters = new List<IParameter>();

                operation.Parameters.Add(new NonBodyParameter
                {
                    Name = "Authorization",
                    In = "header",
                    Description = "JWT access token",
                    Required = true,
                    Type = "string",
                    //Default = $"Bearer {token}"
                });

                operation.Responses.Add("401", new Response { Description = "Unauthorized" });
                operation.Responses.Add("403", new Response { Description = "Forbidden" });

                operation.Security = new List<IDictionary<string, IEnumerable<string>>>();

                //Add JWT bearer type
                operation.Security.Add(new Dictionary<string, IEnumerable<string>>
                {
                    { "Bearer", new string[] { } }
                });
            }
        }
    }

Shortly, this OperationFilter class only adds the locker icon to methods that require Authorization. The locker is always Opened though. So not the perfect solution, but for now is ok.

Here is how it looks:

enter image description here

Note: So if you want to test the API, you first get a token and then fill it where needed.

like image 195
G.Dimov Avatar answered Nov 09 '22 04:11

G.Dimov


For Swashbuckle 5.0.0 and later AddAuthHeaderOperationFilter from @G.Dimov's answer changes to following (with some additional style edits):

public class AddAuthHeaderOperationFilter : IOperationFilter
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public AddAuthHeaderOperationFilter(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Apply(Operation operation, OperationFilterContext context)
    {
        var filterDescriptor = context.ApiDescription.ActionDescriptor.FilterDescriptors;
        var isAuthorized = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
        var allowAnonymous = filterDescriptor.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);

        if (isAuthorized && !allowAnonymous)
        {
            if (operation.Parameters == null)
                operation.Parameters = new List<IParameter>();

            operation.Parameters.Add(new NonBodyParameter
            {
                Name = "Authorization",
                In = "header",
                Description = "JWT access token",
                Required = true,
                Type = "string"
            });

            operation.Responses.Add("401", new Response { Description = "Unauthorized" });
            operation.Responses.Add("403", new Response { Description = "Forbidden" });

            operation.Security = new List<IDictionary<string, IEnumerable<string>>>();

            //Add JWT bearer type
            operation.Security.Add(new Dictionary<string, IEnumerable<string>>
            {
                { "Bearer", new string[] { } }
            });
        }
    }
}

EDIT

Swagger UI refuses to send the Authorization header if it is defined as a parameter. So probably better option will be to create a security definition in the SwaggerGen service configuration (usually in Startup.ConfigureServices):

public void ConfigureServices(IServiceCollection services)
{
    // Service configuration
    services.AddSwaggerGen(c =>
    {
        // Configure Swagger
        // "Bearer" is the name for this definition. Any other name could be used
        c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = "Use bearer token to authorize",
                Type = SecuritySchemeType.Http,
                Scheme = "bearer",
                BearerFormat = "JWT"
            });
    }
}

And then add a security requirement with a reference to a definition to the operation:

public class AddAuthorizationHeaderOperationHeader : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var actionMetadata = context.ApiDescription.ActionDescriptor.EndpointMetadata;
        var isAuthorized = actionMetadata.Any(metadataItem => metadataItem is AuthorizeAttribute);
        var allowAnonymous = actionMetadata.Any(metadataItem => metadataItem is AllowAnonymousAttribute);

        if (!isAuthorized || allowAnonymous)
        {
            return;
        }
        if (operation.Parameters == null)
            operation.Parameters = new List<OpenApiParameter>();

        operation.Security = new List<OpenApiSecurityRequirement>();

        //Add JWT bearer type
        operation.Security.Add(new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {                            
                        Reference = new OpenApiReference
                        {                               
                            Type = ReferenceType.SecurityScheme,
                            // Definition name. 
                            // Should exactly match the one given in the service configuration
                            Id = "Bearer"
                        }
                    }, new string[0]
                }
            }
        );
    }
}
like image 21
Grigorii Hait Avatar answered Nov 09 '22 03:11

Grigorii Hait


Please follow below steps to implement Swagger with correct padlock-

Step -1

Add a class and inherit this class with IOperationFilter interface. After that, implement Apply method definition of IOperationFilter interface.

To implement Apply method you need two parameters of type OpenApiOperation and OpenApiOperation.

public class AddSwaggerService : IOperationFilter
    {     

        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            var actionMetadata = context.ApiDescription.ActionDescriptor.EndpointMetadata;
            var isAuthorized = actionMetadata.Any(metadataItem => metadataItem is AuthorizeAttribute);
            var allowAnonymous = actionMetadata.Any(metadataItem => metadataItem is AllowAnonymousAttribute);

            if (!isAuthorized || allowAnonymous)
            {
                return;
            }
            if (operation.Parameters == null)
                operation.Parameters = new List<OpenApiParameter>();

            operation.Security = new List<OpenApiSecurityRequirement>();

            
            var security = new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,                            
                            Id = "Bearer"
                        }
                    }, new List<string>()
                }
            };
            //add security in here
            operation.Security.Add(security);
        }

Step - 2

Add swagger Generation Service in ConfigureServices method in Startup.cs. And within this Service you need to add below line which we implement in step 1.

c.OperationFilter<AddSwaggerService>();

 public void ConfigureServices(IServiceCollection services)
        {

//.........other Services.........
//.........other Services.........
//.........other Services.........

services.AddSwaggerGen(c =>
                c.SwaggerDoc(AppConstantKeys.APIName, new OpenApiInfo { Title = "title", Version = "APIVersion" });

                c.OperationFilter<AddSwaggerService>();
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description ="SwaggerShortDescription",
                    Name = "HeaderName",
                    In = ParameterLocation.Header,
                    Type = SecuritySchemeType.ApiKey,

                });
            });

//.........other Services.........
//.........other Services.........
//.........other Services.........

}

Step-3 Add swagger in middleware pipeline.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
//.........other middlewares.........
//.........other middlewares.........
//.........other middlewares.........
//.........other middlewares.........


            app.UseSwagger();
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "SwaggerUIName");
                c.DocumentTitle = "SwaggerUITitle";
                c.DocExpansion(DocExpansion.None);
                c.RoutePrefix = string.Empty;
            });

//.........other middlewares.........
//.........other middlewares.........
//.........other middlewares.........
//.........other middlewares.........


}


Step- 4

Build and Run.

enter image description here

like image 36
Vikas Chaturvedi Avatar answered Nov 09 '22 02:11

Vikas Chaturvedi