All projects

Janus

A self-hosted credential boundary. Applications authenticate to Janus; only Janus reads the upstream secret from OpenBao and adds it on the way out. A Java 25 and Spring Boot 4 modular monolith, with a React console for identities, connections and grants. It runs in production, and my other application depends on it.

Status
Maintained, in production
Role
Sole developer, from security model to production.
Presentation
0:00 / 0:00

The initial problem

An API key gets copied into a project, committed by accident, and rotated never. Every application holding a key can leak it. And nothing tells you which service called what, or how often.

The solution

An authenticated proxy. The caller proves its identity, Janus checks the grant, applies the quota, reads the secret from OpenBao, and forwards the request. The secret never crosses back: not in a response, not in a log, not in an error message. Every call is audited under a correlation ID.

Key decisions

Java 25 and Spring Boot 4, the current LTS. Proven with nothing at stake, before production forces the upgrade.

A modular monolith, not microservices. One artifact to deploy. The need did not justify the complexity.

A grant admits everything under a slug by default. An allowlist would only be a second, staler copy of what the upstream already enforces. Scoping exists for the keys that cannot say no themselves.

Architecture

Your application

holds a caller key, never the API's

the request

The API's own key is readable only here

Janus

checks identity, then the grant, then the quota

reads the key

OpenBao

holds the API's key

the same request, with the key added

The third-party API

TMDB, and whatever else is registered

the response comes back scrubbed, under a correlation ID

The application authenticates to Janus, not to the API. Nothing that crosses back carries the key: not a response, not a log, not an error message.

A modular monolith, layered controller to service to repository. The HTTP class decides nothing, the service owns the transaction and the audit record, and entities enforce their own invariants. Two independent security chains: one for the console, one for the gateway. PostgreSQL holds identities, connections, grants and audits, never a plaintext secret. Proxied calls run on virtual threads, so concurrency is bounded by the database pool rather than by the servlet container.

I moved to Java 25 and Spring Boot 4 while nothing was at stake, so the upgrade would not arrive later as an emergency. It runs on a server I administer, and when it breaks I am the one reading the logs.

What is left

The project is maintained, in production.

Publish real screenshots of the console

Document the threat model as a page of its own

Widen coverage on the token exchange paths

Technologies

Java 25 · Spring Boot 4 · Virtual threads · React 19 · TypeScript · Vite · Tailwind CSS · PostgreSQL · OpenBao · Maven · Docker Compose · Traefik · nginx · GitLab CI

Could we work together?

I am looking for a 14-month full-stack apprenticeship starting in September 2026.