AG
TR

// work / fivucsas

FIVUCSAS

A multi-tenant biometric authentication platform — face, voice, MRZ, and active liveness — packaged as an embeddable widget. Self-hosted, KVKK-compliant, WebAuthn-first.

Role
Lead engineer — full-stack, ML, and infrastructure · RollingCat Software
Date
Mar 2026 – Jun 2026

Stack

  • Java 21
  • Spring Boot 3
  • Python
  • FastAPI
  • PostgreSQL 16
  • pgvector
  • React 18
  • Kotlin Multiplatform
  • WebAuthn
  • OAuth 2.0
  • OIDC
  • Flyway
  • Docker
  • Traefik
  • Loki + Promtail + Grafana

The problem

Biometric authentication is hard to adopt because integrating it usually means handling raw face and voice data, defeating spoofing attacks, and meeting strict privacy law — work most teams cannot take on. The goal was to make biometric login as easy to drop into a site as reCAPTCHA, without the integrator ever touching the biometric data.

Constraints

  • Biometric data must never sit unencrypted at rest, and embedding extraction must never be reachable from the public internet.
  • KVKK (Turkish data-protection law) compliance is a hard requirement, not a later add-on.
  • Each tenant organization must be isolated from every other tenant at the database level.

Approach

The platform splits into three runtime axes — a Spring Boot identity core as the source of truth, a private FastAPI ML sidecar that owns all biometric processing, and thin clients (React web, Kotlin Multiplatform mobile and desktop, and an embeddable widget). The widget exposes the whole challenge flow the way reCAPTCHA exposes a challenge.

Key decisions

  • Split the ML sidecar from the identity core as a separate deployable

    The biometric processor talks to the API only over a private Docker network and is never exposed publicly. The ML stack can be upgraded without touching the API, and embedding extraction is structurally unreachable from the internet.

  • Make multi-tenancy a database concern, not an application filter

    Each organization is schema-isolated, so there is no "WHERE tenant_id = ?" clause that a developer can forget to add. Tenant isolation cannot be bypassed by an application bug.

  • Fail fast when the embedding encryption key is missing

    Embeddings are encrypted with Fernet at rest and decrypted only in-process at the moment cosine similarity runs. The app refuses to boot without the key, so it can never silently fall back to a default that would invalidate every stored embedding.

Architecture

Browsers and mobile clients reach the Spring Boot identity core through a Traefik reverse proxy. The identity core owns PostgreSQL 16 with pgvector and talks to the FastAPI biometric processor over a private Docker network that the public internet cannot reach. Loki, Promtail, and Grafana observe the whole stack.

flowchart TB
  client["Clients<br/>React web · KMP mobile/desktop · embeddable widget"]
  traefik["Traefik reverse proxy"]
  api["Identity Core API<br/>Spring Boot 3 · Java 21"]
  db[("PostgreSQL 16<br/>+ pgvector")]
  ml["Biometric Processor<br/>FastAPI · Python"]
  obs["Loki · Promtail · Grafana"]

  client --> traefik --> api
  api --> db
  api -. private docker network .-> ml
  api --> obs
  ml --> obs
Three runtime axes behind a Traefik proxy; the ML sidecar is private-network-only.

Outcome

FIVUCSAS runs in production as a self-hosted multi-tenant platform with an embeddable widget, WebAuthn/FIDO2 passkeys, and a randomized active-liveness challenge that defeats screen-replay and pre-recorded attacks. The repository is private pending a third-party security review; source access is available on request.

By the numbers

  • 55+ Flyway migrations
  • 13 FK-cascaded tables behind the users row
  • 3 Runtime axes (API · ML · clients)
  • Fernet Embedding encryption at rest
  • SHA-256 Model delivery integrity
  • WebAuthn Primary login factor

Deep dive

FIVUCSAS — Face and Identity Verification Using Cloud-based SaaS — began as my senior engineering project at Marmara University and now ships under RollingCat Software, the umbrella name I publish some of my work under. This case study is the architecture story; for the war stories — the three production incidents the team learned the most from — see the companion write-up.

The shape of the system

The core insight that shaped almost every decision was simple to state and hard to enforce: biometric data must never sit unencrypted at rest, and the embedding extraction process must never be reachable from the internet. Everything else fell out of that.

That single constraint is why the ML stack lives in a separate FastAPI sidecar on a private Docker network rather than inside the Spring Boot API. The identity core is the authoritative source of truth for tenants, users, sessions, audit logs, and MFA factors (TOTP, WebAuthn, NFC, biometric). The biometric processor owns the face mesh, embedding extraction, and active-liveness puzzle scoring, and it answers only to the API — never to a browser.

Active liveness as the differentiator

The “Biometric Puzzle” is the part I am proudest of. Instead of a single still frame — which a printed photo or a screen replay can defeat — the widget prompts a randomized sequence of facial actions: smile, blink, look left, look right. The randomization is what makes a pre-recorded attack impractical: the attacker cannot know the sequence in advance. The widget exposes this whole flow the way reCAPTCHA exposes a challenge, so a tenant integrates against the smallest possible surface.

Privacy and tenancy by construction

Two design choices keep the platform honest:

  • Schema-per-tenant isolation. Tenant boundaries are a database concern, not an application filter. There is no shared table where a forgotten predicate leaks one organization’s data to another.
  • Fail-fast configuration. The application refuses to start without the embedding encryption key. A fail-soft default would silently invalidate every stored embedding — exactly the kind of irreversible data corruption that is far better to catch at boot than in production.

Operational posture

The platform is self-hosted on a Hetzner CX43 box behind Traefik, observed with Loki + Promtail + Grafana, and backed up with pgBackRest WAL archiving for point-in-time recovery. Security hygiene is part of the workflow, not an afterthought: gitleaks runs in CI, GitHub secret-scanning and push-protection are on, and every new OAuth endpoint gets a permitAll-chain grep as part of PR review.

The source is private until a third-party security review completes. Source access is available on request.

All case studies