📡 Service Architecture
- Web Service: Blazor Server UI with authentication (Port 8080)
- Application Service: REST API with JWT authentication (Port 8081)
- MongoDB: Document database (Port 27017)
- Redis: Cache and messaging (Port 6379)
The Application Service is a separate ASP.NET Core WebAPI process that handles data-oriented requests. The web application communicates with it via HTTP using JWT authentication, automatically forwarding the current organisation context.
📚 API Documentation
- Health Checks: System Health Status
- Infrastructure: Infrastructure Monitor
🏢 Scoped Controller Pattern
API controllers are categorised into two scopes using attributes and base classes:
Organisation-Scoped
Inherit from OrganisationApiController and are marked with [OrganisationScoped]. These require an X-Organisation-Id header. The ApiTenantResolutionMiddleware resolves the organisation, sets the tenant context, and all IRepository<T> queries are automatically scoped to that org's database.
public class MyController : OrganisationApiController{ // CurrentOrganisationName, CurrentOrganisationId available // IRepository<T> queries scoped to org's database}Platform-Scoped
Inherit from PlatformApiController and are marked with [PlatformScoped]. No organisation context is needed. Used for authentication endpoints and platform-level operations.
[AllowAnonymous]public class TokenController : PlatformApiController{ // No tenant resolution, uses IPlatformRepository<T>}🔐 Authentication
Service-to-Service (Web → API)
The web application authenticates using client credentials. The JwtAuthenticationHandler handles this automatically, caching one token per organisation and stamping the matching X-Organisation-Id header on every request.
Each issued JWT is bound to a specific organisation via an org_id claim. The API rejects any request where the bearer token's org_id does not match the X-Organisation-Id header — a leaked token can only act on the organisation it was minted for. Tokens minted without an OrganisationId are usable only against [PlatformScoped] endpoints.
POST /api/token{ "ClientId": "blazorblueprint-web", "ClientSecret": "...", "OrganisationId": "<org-guid>" // optional; required for [OrganisationScoped] calls}# Subsequent requests:Authorization: Bearer <jwt-with-org_id-claim>X-Organisation-Id: <same-org-guid>Configuration: Client credentials are stored in appsettings.json under Services:ApplicationService. Both base controllers use [Authorize(Policy = "ServiceClient")], so any future co-tenant of the JWT secret can't reach the API without the ServiceClient role.
📋 Current Endpoints
| Method | Route | Scope | Description |
|---|---|---|---|
POST |
/api/token |
Platform | Client credentials token exchange |
GET |
/api/weatherforecast |
Organisation | Weather demo (Redis cached, returns org name) |
GET |
/api/weatherforecast/page-cache |
Organisation | Weather demo (no server cache, returns org name) |
See the Caching Demos to see the tenant context in action — the API response includes the resolved organisation name.
📦 Ready to Download?
Start building with Blazor Blueprint's service architecture and APIs. Download the free Developer version or get Enterprise with premium plugins and commercial licensing.
🔓 Open Source on GitHub • Free for personal/non-commercial use • Enterprise license (£399) required for commercial use • Full source code included