Ktor
ihawu-ktor is the Ktor server adapter — the same zero-wiring experience the
Spring Boot starter gives Spring. A single install(IhawuKtor) masks
every @IhawuResource response for the caller who receives it. It builds on
ihawu-kotlinx (the kotlinx.serialization backend), so it pulls
ihawu-kotlinx and ihawu-core transitively — it is the only dependency you add.
implementation("org.ihawu:ihawu-ktor:0.4.1")Install
Section titled “Install”install(IhawuKtor) { // Map the authenticated call to a caller. No principal -> the response fails closed to {}. resolvePrincipal = { it.ihawuPrincipal() }
// Static role-based rules (or supply your own ResourcePolicyResolver via policyResolver(...)). policies( ResourcePolicy( "employee", mapOf("MANAGER" to listOf(FieldPolicy("ssn", MaskingStrategy.REDACT, "***-**-****"))), ), )
// Register each @IhawuResource serializer under its resource name so it masks when serialized. resources(Employee.serializer() to "employee")}The one install folds in the ContentNegotiation registration — do not install it separately.
What it wires for you
Section titled “What it wires for you”- Response masking — a custom
ContentConverterwraps the kotlinx masking serializer, so every@IhawuResourceresponse is masked as it serializes. Non-resource responses pass through untouched. - Identity bridge —
resolvePrincipalmaps each call to anIhawuPrincipal; the defaultApplicationCall.ihawuPrincipal()reads one straight from KtorAuthentication. The masking context is installed on the send pipeline, so it resolves after route-levelauthenticate { }. - Per-call caching — each
(principal, resource)resolves at most once per call, for free, via the engine’s request-scoped memoization. - Fail-closed — no principal →
{}, matching the other adapters.
Observability & failing the request
Section titled “Observability & failing the request”A fail-closed {} (HTTP 200) is ambiguous — fully masked for this caller or policy resolution failed —
so a policy-store outage can go unnoticed. Two opt-in knobs make it visible:
Failure metrics. Point onFailClosed at a Micrometer registry to count every fail-closed drop as
ihawu.masking.failures{resource,reason} — the same metric the Spring Boot starter emits. Alert on
reason=RESOLVER_ERROR.
install(IhawuKtor) { onFailClosed = MicrometerMaskingFailureSink(appMeterRegistry) // …or log AND count: CompositeMaskingFailureSink(myLogSink, MicrometerMaskingFailureSink(registry))}Fail the request instead of masking. By default a resolver outage masks to {} behind a 200. Set
onPolicyFailure = ResolverErrorMode.FAIL_REQUEST and it instead surfaces as a 500, so the outage is
not silent (only the resolver-error path — a missing principal still masks). Add Ktor’s StatusPages if you
want a custom error body. Best-effort: a resource nested past the point the response is already committed
can’t become a clean 500.
install(IhawuKtor) { onPolicyFailure = ResolverErrorMode.FAIL_REQUEST}Polymorphism
Section titled “Polymorphism”Polymorphic @IhawuResource responses mask too — the concrete subtype is masked and the class
discriminator preserved, in both the default and array-polymorphism encodings, nested and in collections.
- Sealed hierarchies work out of the box.
- OPEN (non-sealed abstract/interface) hierarchies additionally require registering their subtypes on
the
Json’sSerializersModule— pass thatJsonto the plugin viajson = …and the converter threads its module through.
The runnable sample shows both
(/payment sealed, /alert OPEN).
Multiplatform note
Section titled “Multiplatform note”Ktor masking runs on the ihawu-kotlinx backend, which materialises the response tree and rewrites it —
a different cost profile from the Jackson streaming path (see the
backend comparison).
Get started
Section titled “Get started”For a working reference, the
runnable sample shows the plugin
against a live Netty endpoint with two roles and a testApplication test that pins each role’s masked
JSON.
Reference
Section titled “Reference”Full API on the Dokka reference for ihawu-ktor.