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.
The problem is, it also adds an "open" locker icon, next to every method. Even though, some of them require authorization.
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.
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.
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?
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?
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:
Note: So if you want to test the API, you first get a token and then fill it where needed.
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]
}
}
);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With