Attrify.InvisibleApi
0.0.0.2
dotnet add package Attrify.InvisibleApi --version 0.0.0.2
NuGet\Install-Package Attrify.InvisibleApi -Version 0.0.0.2
<PackageReference Include="Attrify.InvisibleApi" Version="0.0.0.2" />
paket add Attrify.InvisibleApi --version 0.0.0.2
#r "nuget: Attrify.InvisibleApi, 0.0.0.2"
// Install Attrify.InvisibleApi as a Cake Addin #addin nuget:?package=Attrify.InvisibleApi&version=0.0.0.2 // Install Attrify.InvisibleApi as a Cake Tool #tool nuget:?package=Attrify.InvisibleApi&version=0.0.0.2
Attrify - Invisible API attibute and middleware
The InvisibleApi
attribute is designed to work with the InvisibleApiMiddleware
which is a lightweight
ASP.NET Core middleware implementation designed to control the visibility of specific API endpoints in your application.
By using the [InvisibleApi] attribute in combination with this middleware, you can make certain endpoints inaccessible
(returning a 404 Not Found) unless specific conditions are met.
This approach is particularly useful for:
- Securing internal endpoints from general exposure.
- Providing an additional layer of security beyond standard authentication.
Overview
The Attrify - Invisible API
is a solution designed to hide specific API endpoints, primarily for acceptance testing purposes.
Developers can uses the [invisibleApi]
attribute to mark Controllers
or Actions
as invisible, eliminating the need for
manual endpoint configuration in middleware. It also introduces an additional security layer by requiring that an authenticated
user must belong to a certain role to access the endpoint. If these conditions are not met, the middleware intercepts the request
and returns a 404 Not Found
response, making the endpoint invisible to unauthorized users.
This implementation is specifically tailored for testing scenarios where endpoints need to be hidden in production environments. It is not intended to be used by consumers (internal or otherwise).
(The Attrify - Invisible API
is an adaptation of the original InvisibleApi created by Hassan Habib)
Key Benefits
- Enhanced Security: Endpoints are only visible to authenticated users with the appropriate roles and a valid request header.
- Ease of Use: A simple attribute-based implementation to control endpoint visibility.
- Scalability: Works seamlessly with ASP.NET Core's middleware pipeline and authorization policies.
Purpose / Use Case
The Invisible API and Middleware is designed to secure and hide internal endpoints used exclusively for acceptance testing, ensuring they remain inaccessible in production environments. These endpoints facilitate tasks such as:
- Setting up test data.
- Running specific test scenarios.
- Tearing down data after testing.
To safeguard these endpoints, the middleware employs a twofold protection mechanism:
- Request Header Validation: A dynamically generated key-value pair must be present in the request headers.
- Role-Based Authorization: The user must be authenticated and belong to a dynamically assigned role.
This middleware enables:
- Consistent data entry and test scenario creation via APIs that enforce business rules and validations negating the need for direct database manipulation.
- End-to-end testing by adhering to production-like constraints.
- Complete concealment of internal endpoints from real users in production or non-test scenarios.
Key Features
Dynamic Security:
- Security values (request header and role) are randomly generated at application startup.
- These values are not stored in configuration files or exposed to real users.
Dependency Injection Integration:
- The generated security values are registered within the DI container, enabling controlled access for acceptance testing without external exposure.
How It Works in Your Application
In your application's Program.cs
or Startup.cs
, a unique request header key-value pair and a required role name are dynamically generated using the InternalVisibleKey
class. This instance is registered in the Dependency Injection (DI) container within the ConfigureServices
method and passed to middleware for runtime enforcement.
Endpoints intended to be hidden are decorated with the [InvisibleApi]
attribute. The middleware evaluates all requests to these endpoints, ensuring:
- The request contains the correct header.
- The user is authenticated and belongs to the specified role.
If validation fails, the middleware responds with a 404 Not Found status, effectively hiding the endpoint from unauthorized users.
How It Works in Your Acceptance Test Project
In the acceptance test project, an API broker class initializes an instance of your web application via Program.cs
or Startup.cs
using a test web application factory.
The test factory inherits from the standard WebApplicationFactory
, retaining its setup but allowing you to:
Override default initialization:
- Replace real authentication and authorization services with a custom authentication scheme (
TestScheme
) using aTestAuthHandler
and a permissive authorization policy (TestPolicy
).
- Replace real authentication and authorization services with a custom authentication scheme (
Simulate authentication and authorization:
- The
TestAuthHandler
is configured to simulate an authenticated user. - Since the DI container is accessible, it retrieves the registered
InternalVisibleKey
instance. - The
TestAuthHandler
dynamically adds the required role to the user identity for authentication and authorization.
- The
Additionally, the HttpClient
in the API broker class is configured to include the custom header key-value pair in all requests, satisfying the middleware's validation. This setup enables the acceptance test project to access hidden endpoints securely, using dynamically generated security values.
Example Use Case
Imagine your application has an API that provides product information. In production, only the GET endpoint is needed, as customers can only view products. However, during acceptance testing, you also need to add, update, and delete products to verify functionality. Instead of bypassing the API and inserting data directly into the database, you use the Invisible API to:
- Create products via the API, ensuring all validation rules are applied.
- Test scenarios that simulate actual user workflows.
- Cleanly tear down test data after tests are complete.
In production, these endpoints remain completely hidden and inaccessible, ensuring they can only be used during controlled testing environments.
Components
[InvisibleApi]
Attribute
The [InvisibleApi]
attribute is used to mark an API endpoint or controller as invisible. If applied, the endpoint will be inaccessible unless:
- A custom header with the correct key-value pair is included in the request.
- The user is authenticated and belongs to a specific role.
Example:
[InvisibleApi]
[HttpPost]
public async ValueTask<ActionResult<Product>> PostProductAsync(Product product)
{
...
// POST logic
...
}
InvisibleApiMiddleware
The middleware evaluates each incoming request:
- It checks if the target endpoint is decorated with
[InvisibleApi]
. - It validates the presence of the custom header and its value.
- It ensures the user is authenticated and belongs to the specified role.
- If any of these checks fail, the middleware returns a
404 Not Found
response, making the endpoint inaccessible.
Implementation
Setup the Web Application
Step 1: Create the key and value for the custom header
Define a custom visibility header by creating an
InvisibleApiKey
:public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var invisibleApiKey = new InvisibleApiKey(); ConfigureServices(builder, builder.Configuration, invisibleApiKey); var app = builder.Build(); ConfigurePipeline(app, invisibleApiKey); app.Run(); } public static void ConfigurePipeline(WebApplication app, InvisibleApiKey invisibleApiKey) { ... } }
Add the middleware to your application's pipeline:
In
Program.cs
orStartup.cs
:public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); var invisibleApiKey = new InvisibleApiKey(); ConfigureServices(builder, builder.Configuration, invisibleApiKey); var app = builder.Build(); ConfigurePipeline(app, invisibleApiKey); app.Run(); } public static void ConfigurePipeline(WebApplication app, InvisibleApiKey invisibleApiKey) { ... app.UseInvisibleApiMiddleware(invisibleApiKey); ... } }
Step 2: Use the [InvisibleApi]
Attribute
Mark the endpoints or controllers you want to make invisible with the [InvisibleApi]
attribute:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : RESTFulController
{
private readonly IProductService productService;
public ProductsController(IProductService productService) =>
this.productService = productService;
[InvisibleApi]
[HttpPost]
public async ValueTask<ActionResult<Product>> PostProductAsync(Product product)
{
...
}
[HttpGet]
public async ValueTask<ActionResult<IQueryable<Product>>> GetAllProductsAsync()
{
...
}
[HttpGet("{productId}")]
public async ValueTask<ActionResult<Product>> GetProductByIdAsync(Guid productId)
{
...
}
[InvisibleApi]
[HttpPut]
public async ValueTask<ActionResult<Product>> PutProductAsync(Product product)
{
...
}
[InvisibleApi]
[HttpDelete("{productId}")]
public async ValueTask<ActionResult<Product>> DeleteProductByIdAsync(Guid productId)
{
...
}
}
In the above sample, the PostProductAsync
, PutProductAsync
, and DeleteProductByIdAsync
endpoints are marked as invisible. These endpoints will only be accessible if the custom
header is present in the request and the user is authenticated with the required role.
Step 3: Test the Configuration
Without Header or Incorrect Role:
- Send a request to an endpoint marked with
[InvisibleApi]
. - The response will be
404 Not Found
if the header is missing or the user does not belong to the required role.
- Send a request to an endpoint marked with
With Correct Header and Role:
- Setup your acceptance test project as per the next section to send a request with the custom header and the correct role.
- The response will be the expected API data.
Setup the Web Application
Step 1: Create a Test Auth Handler
This handler will simulate an authenticated user with the required role.
public class TestAuthHandler : AuthenticationHandler<CustomAuthenticationSchemeOptions>
{
public TestAuthHandler(
IOptionsMonitor<CustomAuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder)
: base(options, logger, encoder)
{ }
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
string randomOidGuid = Guid.NewGuid().ToString();
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "TestUser"),
new Claim(ClaimTypes.Role, "Administrators"),
new Claim("oid", randomOidGuid)
};
var invisibleApiKey = Options.InvisibleApiKey;
if (invisibleApiKey != null && !string.IsNullOrWhiteSpace(invisibleApiKey.Key))
{
claims.Add(new Claim(ClaimTypes.Role, invisibleApiKey.Key));
}
var identity = new ClaimsIdentity(claims, "TestScheme");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "TestScheme");
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
Step 2: Create a Test Web Application Factory
This will allow you to override the default authentication and authorization configuration applying the TestAuthHandler
and TestPolicy
to simulate an authenticated user with the required role.
public class TestWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) =>
{
OverrideSecurityForTesting(services);
});
}
private static void OverrideSecurityForTesting(IServiceCollection services)
{
var invisibleApiKeyDescriptor = services
.FirstOrDefault(d => d.ServiceType == typeof(InvisibleApiKey));
InvisibleApiKey invisibleApiKey = null;
if (invisibleApiKeyDescriptor != null)
{
using (var serviceProvider = services.BuildServiceProvider())
{
invisibleApiKey = serviceProvider.GetService<InvisibleApiKey>();
}
}
var authenticationDescriptor = services
.FirstOrDefault(d => d.ServiceType == typeof(IAuthenticationSchemeProvider));
if (authenticationDescriptor != null)
{
services.Remove(authenticationDescriptor);
}
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "TestScheme";
options.DefaultChallengeScheme = "TestScheme";
})
.AddScheme<CustomAuthenticationSchemeOptions, TestAuthHandler>("TestScheme", options =>
{
options.InvisibleApiKey = invisibleApiKey;
});
services.AddAuthorization(options =>
{
options.AddPolicy("TestPolicy", policy => policy.RequireAssertion(_ => true));
});
}
}
Step 3: Create an API Broker Class
This class initializes the test web application factory and configures an HttpClient
to
automatically include the custom header key-value pair in all requests.
public partial class ApiBroker
{
internal readonly TestWebApplicationFactory<Program> webApplicationFactory;
internal readonly HttpClient httpClient;
internal readonly IRESTFulApiFactoryClient apiFactoryClient;
internal readonly InvisibleApiKey invisibleApiKey;
public ApiBroker()
{
this.webApplicationFactory = new TestWebApplicationFactory<Program>();
this.httpClient = this.webApplicationFactory.CreateClient();
this.httpClient.DefaultRequestHeaders
.Add(this.invisibleApiKey.Key, this.invisibleApiKey.Value);
this.apiFactoryClient = new RESTFulApiFactoryClient(this.httpClient);
this.invisibleApiKey = this.webApplicationFactory.Services.GetService<InvisibleApiKey>();
}
}
**Note:** The `InvisibleApiKey` instance is retrieved from the DI container, providing
access to the dynamically generated key-value pair.
The `ApiBroker` class allows you to securely access hidden endpoints in your acceptance test project.
The custom header and required role are automatically included in all requests, so you don't need to
adapt your tests to include them manually.
### Step 4: Create an acceptance test that will access the invisible endpoints
```csharp
public partial class ProductsApiTests
{
[Fact]
public async Task ShouldPostProductAsync()
{
// given
Product randomProduct = CreateRandomProduct();
Product inputProduct = randomProduct;
Product expectedProduct = inputProduct;
// when
await this.apiBroker.PostProductAsync(inputProduct);
Product actualProduct =
await this.apiBroker.GetProductByIdAsync(inputProduct.Id);
// then
actualProduct.Should().BeEquivalentTo(expectedProduct);
await this.apiBroker.DeleteProductByIdAsync(actualProduct.Id);
}
}
This test will access the PostProductAsync
and DeleteProductByIdAsync
endpoints, which is marked as invisible.
Additional / Advanced (Optional): Hide from Swagger UI
If Swagger is deployed in your production environment, you may want to hide invisible endpoints from the Swagger UI.
To hide these endpoints from Swagger, you can create a custom IDocumentFilter
:
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class InvisibleApiDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var pathsToRemove = swaggerDoc.Paths
.Where(p => context.ApiDescriptions
.Any(api => p.Key.Contains(api.RelativePath) &&
api.CustomAttributes().OfType<InvisibleApiAttribute>().Any()))
.Select(p => p.Key)
.ToList();
foreach (var path in pathsToRemove)
{
swaggerDoc.Paths.Remove(path);
}
}
}
Register it in SwaggerGen
setup:
services.AddSwaggerGen(c =>
{
c.DocumentFilter<InvisibleApiDocumentFilter>();
});
This will ensure that [InvisibleApi]
endpoints are not displayed in the Swagger UI.
Acknowledgements
This version of the Invisible API is inspired by the work of Hassan Habib. His original implementation, InvisibleApi, served as the foundation for this adaptation.
You can find the original code and more details here:
- GitHub: InvisibleApi
- YouTube: Invisible API Middleware
Conclusion
The InvisibleApiMiddleware
and [InvisibleApi]
attribute offer a robust solution for controlling API endpoint visibility.
This approach enhances your application's security by requiring both valid headers and role-based authentication for access,
making it an excellent choice for securing sensitive or administrative APIs.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. |
-
net9.0
- Microsoft.OpenApi (>= 1.6.22)
- Swashbuckle.AspNetCore.SwaggerGen (>= 7.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Attrify.InvisibleApi:
Package | Downloads |
---|---|
Attrify
A library to enhancing REST API functionality through attributes. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.0.0.2 | 91 | 11/24/2024 |
Initial release of InvisibleApi Attrify library.