NDB.Platform.Api
1.0.0
dotnet add package NDB.Platform.Api --version 1.0.0
NuGet\Install-Package NDB.Platform.Api -Version 1.0.0
<PackageReference Include="NDB.Platform.Api" Version="1.0.0" />
<PackageVersion Include="NDB.Platform.Api" Version="1.0.0" />
<PackageReference Include="NDB.Platform.Api" />
paket add NDB.Platform.Api --version 1.0.0
#r "nuget: NDB.Platform.Api, 1.0.0"
#:package NDB.Platform.Api@1.0.0
#addin nuget:?package=NDB.Platform.Api&version=1.0.0
#tool nuget:?package=NDB.Platform.Api&version=1.0.0
NDB.Platform.Api
<div align="center">
The web API layer library for every NDB Platform project
Built by PT. Navigate Digital Boundaries — Navigate Digital Boundaries
</div>
What is NDB.Platform.Api?
NDB.Platform.Api is the web layer package in the NDB Platform ecosystem. It wraps the repetitive ASP.NET Core wiring that every API project needs into consistent, pre-configured building blocks.
Depends on: NDB.Platform.Core + NDB.Platform.Data (both installed automatically).
| Area | What you get |
|---|---|
| Authentication | JWT Bearer dual-token, HMAC-SHA256, fail-fast config validation, token auto-refresh |
| Authorization | Permission-based ([RequirePermission]) + role-based ([NdbRequireRole]) |
| Result → HTTP | Auto-converts handler Result<T> return values to ApiResponse JSON |
| Middleware | Correlation ID, request logging, global exception handler |
| Hangfire | Provider-agnostic background jobs, timing-safe dashboard auth |
| Swagger | Pre-configured Swashbuckle with JWT Bearer support |
| Extras | CORS, ForwardedHeaders, health checks, API versioning, PDF renderer abstraction |
Installation
dotnet add package NDB.Platform.Api
NDB.Platform.CoreandNDB.Platform.Dataare installed automatically as dependencies.
Table of Contents
| Namespace | Documentation | Description |
|---|---|---|
NDB.Platform.Api.Authentication |
📄 Authentication | JWT Bearer, token service, token refresh, actor accessor |
NDB.Platform.Api.Authorization |
📄 Authorization | Permission-based + role-based authorization |
NDB.Platform.Api.Filters |
📄 Filters | ApiResponse envelope, ResultActionFilter, ResultExtensions |
NDB.Platform.Api.Middleware |
📄 Middleware | Correlation ID, request logging, global exception handler |
NDB.Platform.Api.Hangfire |
📄 Hangfire | Background jobs, dashboard auth, provider-agnostic setup |
NDB.Platform.Api.Swagger |
📄 Swagger | Swashbuckle pre-config with JWT and API versioning |
| — | 🧪 Tests | Test suite — net8.0 + net10.0, coverage ≥ 80% |
Minimum Setup
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddNdbCqrs(typeof(Program).Assembly);
builder.Services.AddMediator(opt => opt.ServiceLifetime = ServiceLifetime.Scoped);
builder.Services.AddNdbMapping(typeof(Program).Assembly);
builder.Services.AddNdbJwt(o =>
{
o.Issuer = builder.Configuration["Jwt:Issuer"]!;
o.Audience = builder.Configuration["Jwt:Audience"]!;
o.SigningKey = builder.Configuration["Jwt:Key"]!;
});
builder.Services.AddNdbMiddleware();
builder.Services.AddNdbSwagger(o => o.Title = "My API");
builder.Services.AddNdbHealthChecks();
builder.Services.AddControllers();
var app = builder.Build();
app.UseNdbMiddleware();
app.UseAuthentication();
app.UseAuthorization();
app.UseNdbSwaggerUI();
app.MapControllers();
app.MapNdbHealthChecks();
app.Run();
Core Concepts
1 · JWT Authentication
Dual-token (access + refresh), HMAC-SHA256, with fail-fast startup validation.
builder.Services.AddNdbJwt(o =>
{
o.Issuer = "my-api";
o.Audience = "my-client";
o.SigningKey = "your-32-char-minimum-secret-key!!";
o.AccessTokenLifetime = TimeSpan.FromMinutes(15); // default
o.RefreshTokenLifetime = TimeSpan.FromDays(7); // default
o.RequireHttpsMetadata = false; // dev only
});
NdbJwtOptionsValidator fires at startup and rejects if SigningKey is under 32 characters or Issuer/Audience is missing.
Issue tokens in your login handler:
public class LoginHandler(ITokenIssuer tokenIssuer, AppDbContext db)
: ICommandHandler<LoginCommand, Result<LoginResponse>>
{
public async ValueTask<Result<LoginResponse>> Handle(LoginCommand cmd, CancellationToken ct)
{
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == cmd.Email, ct);
if (user is null || !PasswordHasher.Verify(cmd.Password, user.PasswordHash))
return Result.Unauthorized("Invalid credentials.");
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Role, user.Role)
};
return Result.Success(new LoginResponse(
AccessToken: tokenIssuer.IssueAccessToken(claims),
RefreshToken: tokenIssuer.IssueRefreshToken()));
}
}
When a token is expired, NdbJwtBearerEvents appends Token-Expired: true to the 401 response — so clients can distinguish expiry from invalid credentials before attempting a refresh.
Configurable token refresh endpoint:
builder.Services.AddNdbJwt(o =>
{
// ...
o.RefreshBaseAddress = "https://auth.myapp.com";
o.RefreshEndpoint = "/api/v1/auth/refresh";
o.RefreshClientName = "NdbTokenRefresher";
});
2 · Authorization
Permission-based (granular RBAC)
// Register:
builder.Services.AddNdbPermissionAuthorization(o =>
{
o.SuperAdminClaim = "is_superadmin"; // JWT claim that bypasses all checks
o.BypassRoles = ["SUPER_ADMIN"];
});
builder.Services.AddScoped<IPermissionResolver, PermissionResolver>();
// Use on controllers or actions:
[RequirePermission("users.create")]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest req, CancellationToken ct)
=> (await Mediator.Send(new CreateUserCommand(req), ct)).ToActionResult();
PermissionPolicyProvider generates "perm:{key}" policies on demand — no startup boilerplate per key.
Role-based (shorthand)
[NdbRequireRole("ADMIN")]
public IActionResult AdminOnly() { ... }
[NdbRequireRole("ADMIN", "MANAGER")]
public IActionResult AdminOrManager() { ... }
Secure all endpoints by default
builder.Services.AddNdbAuthorization();
// All endpoints require authentication unless decorated with [AllowAnonymous]
3 · Result → HTTP (ApiResponse Envelope)
ResultActionFilter converts handler return values to a consistent JSON envelope automatically.
builder.Services.AddControllers(opt => opt.Filters.Add<ResultActionFilter>());
| Handler return | HTTP | Response body |
|---|---|---|
Result.Success(dto) |
200 | { success: true, data: dto } |
Result.NotFound("...") |
404 | { success: false, message: "..." } |
Result.Validation(errors) |
400 | { success: false, validationErrors: {...} } |
PagedResult<T>.Success(...) |
200 | { success: true, data: { items, pageInfo } } |
Or call ToActionResult() directly when you need explicit control:
return (await Mediator.Send(query, ct)).ToActionResult();
4 · BaseController
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class UsersController : BaseController
{
[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
=> (await Mediator.Send(new GetUserQuery(id), ct)).ToActionResult();
[HttpDelete("{id}")]
[RequirePermission("users.delete")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
=> (await Mediator.Send(new DeleteUserCommand(id, CurrentUserId!), ct)).ToActionResult();
}
BaseController provides: Mediator (lazy), CurrentUserId, CurrentUserName, CurrentUserRole.
5 · Middleware
app.UseNdbMiddleware();
// 1. GlobalExceptionHandler — unhandled exceptions → 500 ApiResponse JSON
// 2. CorrelationIdMiddleware — reads/generates X-Correlation-ID, echoes in response
// 3. RequestLoggingMiddleware — logs METHOD PATH → STATUS in Xms actor=userId
Slow requests log a Warning at the configured threshold:
RequestLoggingMiddleware.SlowRequestThresholdMs = 300; // default: 500ms
6 · Hangfire
builder.Services.AddNdbHangfire(
configure: o =>
{
o.WorkerCount = 10;
o.Queues = ["default", "critical"];
o.BasicAuthPassword = cfg["Hangfire:Password"]!;
},
storageCallback: cfg => cfg.UsePostgreSqlStorage(connectionString));
app.UseNdbHangfireDashboard(o =>
{
o.DashboardUrl = "/jobs";
o.BasicAuthUser = "admin";
o.BasicAuthPassword = cfg["Hangfire:Password"]!;
});
Password is validated at startup. Basic Auth comparison uses CryptographicOperations.FixedTimeEquals (timing-safe).
7 · Swagger
builder.Services.AddNdbSwagger(o =>
{
o.Title = "My API";
o.Version = "v1";
o.JwtAuthEnabled = true;
});
app.UseNdbSwaggerUI(o => o.RoutePrefix = "docs");
8 · Additional Features
CORS:
builder.Services.AddNdbCors(o =>
{
o.AllowedOrigins = ["https://app.example.com"];
o.AllowCredentials = true;
});
app.UseCors("NdbDefaultCors");
Forwarded Headers (Nginx, Traefik):
builder.Services.AddNdbForwardedHeaders(o =>
{
o.ClearExistingKnownHosts = true;
o.KnownProxies.Add(IPAddress.Parse("10.0.0.1"));
});
app.UseNdbForwardedHeaders(); // must be first in the pipeline
Health Checks:
builder.Services.AddNdbHealthChecks();
builder.Services.AddHealthChecks()
.AddNpgSql(connectionString, tags: ["ready"]);
app.MapNdbHealthChecks();
// GET /health/live → liveness
// GET /health/ready → readiness (tags: ["ready"])
API Versioning:
builder.Services.AddNdbApiVersioning();
// /api/v1/users, /api/v2/users — defaults to v1.0
PDF Renderer:
builder.Services.AddScoped<IPdfRenderer, PlaywrightPdfRenderer>();
// In controller:
return PdfService.ToPdfResult(pdfBytes, "invoice-2024-001");
Requirements
| Requirement | Detail |
|---|---|
| .NET | 8.0 or 10.0 |
| NDB.Platform.Core | 1.0.0 — installed automatically |
| NDB.Platform.Data | 1.0.0 — installed automatically |
| Hangfire storage | Install separately (Hangfire.PostgreSql, Hangfire.SqlServer, etc.) |
IPermissionResolver |
Required when using [RequirePermission] |
IPdfRenderer |
Required when using PdfService.ToPdfResult |
Ecosystem
| Package | Version | Description |
|---|---|---|
| NDB.Platform.Core | 1.0.0 | Foundation — Result pattern, CQRS, utilities, shared contracts |
| NDB.Platform.Data | 1.0.0 | Data layer — audit trail, EF extensions, CodeGen |
| NDB.Platform.Api | 1.0.0 | Web layer ← you are here |
Repository
github.com/ndbco/NDB.Platform.Api
About NDB
PT. Navigate Digital Boundaries — Indonesian technology company focused on enterprise digital platform development.
🌐 ndb.co.id — Navigate Digital Boundaries
License
GPL v3 — Copyright © PT. Navigate Digital Boundaries
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Asp.Versioning.Mvc (>= 8.1.0)
- Asp.Versioning.Mvc.ApiExplorer (>= 8.1.0)
- Hangfire.AspNetCore (>= 1.8.20)
- Hangfire.Core (>= 1.8.20)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.14)
- Microsoft.IdentityModel.Tokens (>= 8.1.0)
- NDB.Platform.Core (>= 1.0.0)
- NDB.Platform.Data (>= 1.0.0)
- Newtonsoft.Json (>= 13.0.3)
- Swashbuckle.AspNetCore (>= 7.3.1)
- Swashbuckle.AspNetCore.Annotations (>= 7.3.1)
- System.IdentityModel.Tokens.Jwt (>= 8.1.0)
-
net8.0
- Asp.Versioning.Mvc (>= 8.1.0)
- Asp.Versioning.Mvc.ApiExplorer (>= 8.1.0)
- Hangfire.AspNetCore (>= 1.8.20)
- Hangfire.Core (>= 1.8.20)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.14)
- Microsoft.IdentityModel.Tokens (>= 8.1.0)
- NDB.Platform.Core (>= 1.0.0)
- NDB.Platform.Data (>= 1.0.0)
- Newtonsoft.Json (>= 13.0.3)
- Swashbuckle.AspNetCore (>= 7.3.1)
- Swashbuckle.AspNetCore.Annotations (>= 7.3.1)
- System.IdentityModel.Tokens.Jwt (>= 8.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 91 | 5/31/2026 |