Filtering Some Methods on Swagger By PermissionID on .Net 6.0

Bora Kaşmer
Geek Culture
Published in
7 min readMar 15, 2022

Today we will talk about filtering the swagger. As seen below, our swagger document is public and can be seen by everyone. But what if we don’t want some methods to show up by some clients for security or business decisions.

Swashbuckle.Application.SwaggerDocsConfig.DocumentFilter()

Swagger methods can be filtered, by using “DocumentFilter”. The key is “swaggerDoc.Paths”. Swagger recognizes methods by their paths. We call it routing. On the below, we see “GetAllUsersByTable()” method’s routing. So swagger know this method as a “/user/getallusersbytable/{tablename}

If we want to hide this method from some users, we have to say to swagger don’t bring this method by its path.

swaggerDoc.Paths.Remove("/user/getallusersbytable/{tablename}")
Swagger Filter By UrlID

Fill SQL Table By Swagger Document (KEY) Method’s Path:

Firstly we need to collect all these swagger document keys, with a uniqueID in a SQL Table. Create Swagger_Service table as below.

CREATE TABLE [dbo].[SWAGGER_SERVICE](
[Id] [int] IDENTITY(1,1) NOT NULL,
[ServiceKey] [nvarchar](500) NOT NULL,
[CreDate] [datetime] NOT NULL,
[IsDeleted] [bit] NOT NULL,
CONSTRAINT [PK_SwaggerService] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[SWAGGER_SERVICE] ADD CONSTRAINT [DF_SwaggerService_CreDate] DEFAULT (getdate()) FOR [CreDate]
GO
ALTER TABLE [dbo].[SWAGGER_SERVICE] ADD CONSTRAINT [DF_SwaggerService_IsDeleted] DEFAULT ((0)) FOR [IsDeleted]
GO

It is time to fill this Swagger_Service table automaticly for once.

DocumentFilter : We need the give a document to swagger for filtering while configuring it. We will create an Empty DocumentFilter as Below. It is inherited from the “IDocumentFilter” interface and Implemented the “Apply()” method.

Infrastructure/CustomSwaggerfilter.cs/Apply()(1):

public class CustomSwaggerFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
//DO YOUR FILTERS
}

We have to add this Document Filter to the Swagger in “Startup.cs” on the “AddSwaggerGen()” method for security. And for extra caution, I prefer to remove all Scheme of the methods at the end of the swagger document on the “UseSwaggerUI()” method as below:

Startup.cs:

services.AddSwaggerGen(c =>
{
c.DocumentFilter<CustomSwaggerFilter>();//For Swagger CustomFilter
.
.
}
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.DefaultModelsExpandDepth(-1);//Remove Schema on Swagger UI
.
.
});

Infrastructure/CustomSwaggerfilter.cs/Apply()(2):

We created the Swagger_Service table, which is saved all method’s paths. And we created DocumentFilter. Now it is time to fill all data to MsSqlDB once for all :) This code block must work only once

  • Firstly we got all method’s paths from => “swaggerDoc.Paths
  • We used Entity DBContext for DB tools. We got DBContext by using the “GetVbtContext()” method, which I will show next chapter.
  • We got all Keys and removed the “/api/” tag and converted all characters to lower for more readable data.
  • SwaggerService is our Entity model. We filled it, with all swaggerDoc keys.
  • AddRange(swaggerList) => If (swaggerList.Count > 3) then all data will Bulk Insert to the DB, if the length is less than three, every data will Insert one by one with .Net 6.0 EntityFramework.

Infrastructure/CustomSwaggerfilter.cs/Apply()(2):

public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
//WORK ONCE! INSERT DB.SwaggerService
using (var dbContext = GetVbtContext("DefaultConnection"))
{
var swaggerList = (from keyPath in swaggerDoc.Paths
select new SwaggerService
{
ServiceKey = keyPath.Key.Replace("/api/", "").ToLower()
}).ToList();
dbContext.SwaggerService.AddRange(swaggerList);
dbContext.SaveChanges();
}
}
//--------------------------------------------------

Infrastructure/CustomSwaggerfilter.cs/GetVbtContext():

This is how we create the Entity VbtContext for using MsSqlDB operations.

public VbtContext GetVbtContext(string connection)
{
string connectionString = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build().GetConnectionString(connection);
var optionsBuilder = new DbContextOptionsBuilder<DashboardContext>(); optionsBuilder.UseSqlServer(connectionString);
return new VbtContext(optionsBuilder.Options);
}

After we inserted all keys to the SWAGGER_SERVICE table, they look as below. These keys are all WebService paths that we declared top of the Methods.

SWAGGER_SERVICE TABLE

Create Dummy Data for Permitted Paths of Users

Now it is time to create dummy data, who can see some methods and who can not.

Create “User_Swagger” Table as below:

  • “UrlId” is the unique id for every user. We could use “IdUser” too. But for security, I preferred at least 6 digits unique number per user. We will use it, for filtering the swagger’s path for every user.
  • “IdUser” is the user’s unique ID. It is related to [DB_USER] table.
  • “IdSwagger” is related to the “[SWAGGER_SERVICE]” table.
CREATE TABLE [dbo].[USER_SWAGGER]([Id] [int] IDENTITY(1,1) NOT NULL,
[UrlId] [int] NULL,
[IdUser] [int] NOT NULL,
[IdSwagger] [int] NOT NULL,
[CreDate] [datetime] NOT NULL,
[IsDeleted] [bit] NOT NULL,
CONSTRAINT [PK_USER_SWAGGER] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[USER_SWAGGER] ADD CONSTRAINT [DF_USER_SWAGGER_CreDate] DEFAULT (getdate()) FOR [CreDate]
GO
ALTER TABLE [dbo].[USER_SWAGGER] ADD CONSTRAINT [DF_USER_SWAGGER_IsDeleted] DEFAULT ((0)) FOR [IsDeleted]
GO
ALTER TABLE [dbo].[USER_SWAGGER] WITH CHECK ADD CONSTRAINT [FK_USER_SWAGGER_DB_USER] FOREIGN KEY([IdUser]) REFERENCES [dbo].[DB_USER] ([IdUser])
GO
ALTER TABLE [dbo].[USER_SWAGGER] CHECK CONSTRAINT [FK_USER_SWAGGER_DB_USER]
GO
ALTER TABLE [dbo].[USER_SWAGGER] WITH CHECK ADD CONSTRAINT [FK_USER_SWAGGER_SWAGGER_SERVICE] FOREIGN KEY([IdSwagger]) REFERENCES [dbo].[SWAGGER_SERVICE] ([Id])
GO
ALTER TABLE [dbo].[USER_SWAGGER] CHECK CONSTRAINT [FK_USER_SWAGGER_SWAGGER_SERVICE]
GO

Now it is time to fill “User_Swagger” table with Dummy Data

SqlServer(Row SQL): We will execute these queries for creating dummy data of 3 user permissions as seen below.

INSERT INTO USER_SWAGGER (UrlId,IdUser,IdSwagger)
SELECT LEFT(CAST(RAND()*1000000000+999999 AS INT),6) as UrlId,
U.Id as IdUser,
SS.Id as IdSwagger
from [dbo].[SWAGGER_SERVICE] AS SS,Users as U
WHERE SS.Id%2=0 and U.Id=1
INSERT INTO USER_SWAGGER (UrlId,IdUser,IdSwagger)
SELECT LEFT(CAST(RAND()*1000000000+999999 AS INT),6) as UrlId,
U.Id as IdUser,
SS.Id as IdSwagger
from [dbo].[SWAGGER_SERVICE] AS SS,Users as U
WHERE SS.Id%3=0 and U.Id=2
INSERT INTO USER_SWAGGER (UrlId,IdUser,IdSwagger)
SELECT LEFT(CAST(RAND()*1000000000+999999 AS INT),6) as UrlId,
U.Id as IdUser,
SS.Id as IdSwagger
from [dbo].[SWAGGER_SERVICE] AS SS,Users as U
WHERE SS.Id%10=0 and U.Id=3

After we inserted all data into the USER_SWAGGER table, they look as below.

USER_SWAGGER TABLE

Filtering Swagger Document:

Startup.cs: Firstly, we have to allow access to the HttpContext, to get from anywhere from the project on startup.cs as below.

services.AddHttpContextAccessor();

CustomSwaggerFilter.cs:

Now let’s get the parameter “Id” from the URL and filter the swagger.

  • We will get the current HttpContect by using HttpContextAccessor. We will use it for getting the URL path.
  • We will get Url by using “_httpContext”. And parsing ID from URL by using “PareQueryString()” method. And finally, we will check “id” is null or not.
  • We will use Entity 6.0 for DB operations. So we will create “dbContext” with “Using{}”. We will get the all User accessible method’s paths from the “UserSawagger” table by using the unique Url parameter Id. It is unique 6 digit [USER_SWAGGER] UrlId. And we set it swaggerKeys variable.

Not: Don’t forget the use “AsNoTracking()” method for improve entity performance, when only reading data.

  • We will exclude all method paths from the project which is not included in the user’s permitted paths. Long story short, we will get the forbidden paths for this user. And we will remove these excluded paths from the “swaggerDoc.Paths” by using “Remove()” method. And finally, this user will not see some paths, which has or her not permitted to access.

CustomSwaggerFilter.cs:

public class CustomSwaggerFilter : IDocumentFilter
{
public HttpContext _httpContext => new HttpContextAccessor().HttpContext;
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
//http://localhost:1923/swagger/index.html?id=12345
Uri url = new Uri(_httpContext.Request.Headers.Referer[0]);
string id = HttpUtility.ParseQueryString(url.Query).Get("id");
if (id != null)
{
using (var dbContext = GetVbtContext("DefaultConnection"))
{
var swaggerKeys = dbContext.UserSwagger.Where(us => us.UrlId == int.Parse(id)).Select(u => u.IdSwaggerNavigation.ServiceKey.Replace("/api/", "").ToLower()).AsNoTracking().ToList();

var unPermittedMethods = swaggerDoc.Paths.Where(x => !swaggerKeys.Contains(x.Key.Replace("/api/", "").ToLower())).ToList();
unPermittedMethods.ForEach(x => { swaggerDoc.Paths.Remove(x.Key); });
}
}
}
}

Conclusion:

Swagger is a public document. But in some cases, we may want to hide some routes for specific roles or users. One of the important things is, keeping updated to the “USER_SWAGGER” table. In the working process, Controllers can be deleted or Updated. And while the developing period, new controllers can be added. So maybe it is better to update the “USER_SWAGGER” table automatically by using microservice or daily jobs.

For security reasons, if you encrypt the UrlID and decrypt it while reading, could be safer. Because a readable 6 digit number could be easy to remember.

I hope this article helped you to understand how to filter methods on the swagger document. See you later until the next article. Bye.

“If you have read so far, first of all, thank you for your patience and support. I welcome all of you to my blog for more!”

Source:

--

--

Bora Kaşmer
Geek Culture

I have been coding since 1993. I am computer and civil engineer. Microsoft MVP. Software Architect(Cyber Security). https://www.linkedin.com/in/borakasmer/