ASP.NET
Core Swagger
Authorized Protected API
▌Introduction
The
OpenAPI Specification from Swagger doesn’t have Authorization information on
protected APIs in default.
In this
article, we will try to add the Authorization information to the OpenAPI
Specification and enable entering JWT for trying the request(s) on Swagger.
The
result will be like this,
1. Swagger show [Authorize] button that will pop up an input
modal for Authentication information.
2. The protected API(s) will shows a lock icon.
A demo:
▋Related articles
▌Environment
▋Dotnet Core SDK 3.1.301
▋Swashbuckle.AspNetCore 5.4.1
▌Implement
▋Install Nuget packages
Name
|
Description
|
Version (In this tutorial)
|
Swagger tools for documenting APIs
built on ASP.NET Core.
|
5.4.1
|
|
A service API versioning library
for Microsoft ASP.NET Core.
|
4.1.1
|
|
ASP.NET Core MVC API explorer
functionality for discovering metadata such as the list of API-versioned
controllers and actions, and their URLs and allowed HTTP methods.
|
4.1.1
|
|
(Optional)
ASP.NET Core middleware that enables an application to receive an OpenID Connect bearer token. |
3.1.6
|
▋Basic Swagger configuration
If you
are on a new ASP.NET Core project, pls follow the steps on this
tutorial to complete the basic
Swagger configuration.
I will
make a quick sample codes as following.
▋ SwaggerConfig.cs
public class SwaggerConfig : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider provider;
public SwaggerConfig(IApiVersionDescriptionProvider provider)
{
this.provider = provider;
}
public void Configure(SwaggerGenOptions options)
{
foreach (var description in this.provider.ApiVersionDescriptions)
{
var info = new Microsoft.OpenApi.Models.OpenApiInfo()
{
Title = $"MyApp {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
};
if (description.IsDeprecated)
{
info.Description += " This API version has been deprecated.";
}
options.SwaggerDoc(description.GroupName, info);
}
}
}
▋ Startup.cs: ConfigureServices
The OperationFilter which implements Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter is for binding the parameters from OpenApiParamAttribute
to OpenAPI document.
public void ConfigureServices(IServiceCollection services)
{
#region API Versioning
services.AddApiVersioning(opt =>
{
opt.ReportApiVersions = true; // List supported versons on Http header
opt.DefaultApiVersion = new ApiVersion(1, 0); // Set the default version
opt.AssumeDefaultVersionWhenUnspecified = true; // Use the api of default version
opt.ApiVersionSelector = new CurrentImplementationApiVersionSelector(opt); // Use the api of latest release number
});
#endregion
#region API Document (Swagger)
services.AddVersionedApiExplorer(options => options.GroupNameFormat = "'v'VVV");
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, SwaggerConfig>();
services.AddSwaggerGen(c =>
{
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = System.IO.Path.Combine(System.AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
#endregion
}
▋ Startup.cs: Configure
public void Configure(
IApplicationBuilder app,
IApiVersionDescriptionProvider provider)
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerEndpoint(
$"/swagger/{description.GroupName}/swagger.json",
description.GroupName.ToUpperInvariant());
}
});
}
With the
above codes, we can have a basic Swagger UI.
Next we
will focus on how to add the Authorization information on protected APIs.
▋Add Security Scheme to Swagger
When
configuring the Swagger generation options, add Security Definition and Security Requirement with our custom JWT
Security Scheme.
A
Security Definition describes how your API is protected.
A Security
Requirement defines a dictionary of required schemes.
services.AddSwaggerGen(c =>
{
// … skip
// Add JWT Authentication
// See https://swagger.io/docs/specification/authentication/bearer-authentication/
var securityScheme = new OpenApiSecurityScheme
{
Name = "JWT Authentication",
Description = "Enter JWT Bearer token **_only_**",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = "bearer",
Type = ReferenceType.SecurityScheme
}
};
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
// Note: The following configuration will make ALL APIs as protected!
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{securityScheme, new string[] { }}
});
});
However,
adding the global Security Requirement will makes all the APIs as protected APIs on Swagger, even if some of them are allow-anonymous.
If not
all of our APIs are protected (in other words, need to be authorized by JWT), we
have not to add the Security Requirement globally.
Lets
remove the following codes and we will create custom OperationFilter for only
adding Security Requirement on protected APIs.
// Note: The following configuration will make ALL APIs as protected!
// // c.AddSecurityRequirement(new OpenApiSecurityRequirement
// // {
// // {securityScheme, new string[] { }}
// // });
▋Create Custom OperationFilter
Here
we create a custom OperationFilter that only apply Security Requirement to
protected APIs.
[HttpPost("RevokeToken")]
[Authorize]
public async Task<string> RevokeToken([FromBody] string token)
We
can distinguish the protected APIs by it.
▋
AuthorizationOperationFilter.cs
public class AuthorizationOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Get Authorize attribute
var attributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>();
if (attributes != null && attributes.Count() > 0)
{
var attr = attributes.ToList()[0];
// Add response types on secure APIs
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
// Add what should be show inside the security section
IList<string> securityInfos = new List<string>();
securityInfos.Add($"{nameof(AuthorizeAttribute.Policy)}:{attr.Policy}");
securityInfos.Add($"{nameof(AuthorizeAttribute.Roles)}:{attr.Roles}");
securityInfos.Add($"{nameof(AuthorizeAttribute.AuthenticationSchemes)}:{attr.AuthenticationSchemes}");
operation.Security = new List<OpenApiSecurityRequirement>()
{
new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "bearer", // Must fit the defined Id of SecurityDefinition in global configuration
Type = ReferenceType.SecurityScheme
}
},
securityInfos
}
}
};
}
}
}
And
we can apply the OperationFilter to Swagger Generation Configuration,
services.AddSwaggerGen(c =>
{
// … skip
// Set the custom operation filter
c.OperationFilter<AuthorizationOperationFilter>();
// Add JWT Authentication
// See https://swagger.io/docs/specification/authentication/bearer-authentication/
var securityScheme = new OpenApiSecurityScheme
{
Name = "JWT Authentication",
Description = "Enter JWT Bearer token **_only_**",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = AuthenticationScheme.Bearer,
BearerFormat = "JWT",
Reference = new OpenApiReference
{
Id = AuthenticationScheme.Bearer,
Type = ReferenceType.SecurityScheme
}
};
c.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme);
});
}
Now
we only have the lock icon on protected APIs, not all of the APIs.
By
clicking the [Authorize] button or the lock icon, we can enter the JWT to
access the protected API on Swagger.
We
can see that when trying the request on Swagger, the JWT will be added to the
Authorization header, and so that we can access the protected API with no
problem.
▋(Optional) For Basic Authentication
Here
are the sample codes for adding Security Scheme of Basic Authentication to
Swagger.
▋ Startup.cs: ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
// … skip
services.AddSwaggerGen(c =>
{
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = System.IO.Path.Combine(System.AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// Set the custom operation filter
c.OperationFilter<AuthorizationOperationFilter>();
// Add Basic Authentication
var basicSecurityScheme = new OpenApiSecurityScheme
{
Name = "Basic Authentication",
Type = SecuritySchemeType.Http,
Scheme = AuthenticationScheme.Basic,
Reference = new OpenApiReference
{
Id = AuthenticationScheme.Basic,
Type = ReferenceType.SecurityScheme }
};
c.AddSecurityDefinition(basicSecurityScheme.Reference.Id, basicSecurityScheme);
});
}
▋ AuthorizationOperationFilter.cs
public class AuthorizationOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Get Authorize attribute
var attributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
.Union(context.MethodInfo.GetCustomAttributes(true))
.OfType<AuthorizeAttribute>();
if (attributes != null && attributes.Count() > 0)
{
var attr = attributes.ToList()[0];
// Add response types on secure APIs
operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" });
// Add what should be show inside the security section
IList<string> securityInfos = new List<string>();
securityInfos.Add($"{nameof(AuthorizeAttribute.Policy)}:{attr.Policy}");
securityInfos.Add($"{nameof(AuthorizeAttribute.Roles)}:{attr.Roles}");
securityInfos.Add($"{nameof(AuthorizeAttribute.AuthenticationSchemes)}:{attr.AuthenticationSchemes}");
operation.Security = new List<OpenApiSecurityRequirement>()
{
new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "basic",
Type = ReferenceType.SecurityScheme
}
},
securityInfos
}
}
};
}
}
}
The Basic
Authentication popup is like this,
▌Reference
沒有留言:
張貼留言