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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NDB.Platform.Api" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NDB.Platform.Api" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="NDB.Platform.Api" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NDB.Platform.Api --version 1.0.0
                    
#r "nuget: NDB.Platform.Api, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NDB.Platform.Api@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NDB.Platform.Api&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=NDB.Platform.Api&version=1.0.0
                    
Install as a Cake Tool

NDB.Platform.Api

<div align="center">

The web API layer library for every NDB Platform project

Built by PT. Navigate Digital BoundariesNavigate Digital Boundaries

NuGet NuGet Downloads License: GPL v3 .NET

</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.Core and NDB.Platform.Data are 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.idNavigate Digital Boundaries


License

GPL v3 — Copyright © PT. Navigate Digital Boundaries

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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