Here's a bug that has shipped to production in more companies than anyone wants to admit:
const payload = jwt.verify(token, publicKey);
// looks fine. it isn't.
Valid signature. Right issuer. Not expired. And minted for a different application. One your user also has access to. Maybe one that isn't even yours. Nothing in that line checks who the token was for.
That's the aud claim. It's optional in most libraries, off by default in a lot of setups, and the single most common way an SSO integration turns into a lateral-movement path.
We got tired of writing that check in code review comments, so we built an identity platform where it isn't a check you can forget.
What Latch Vector is
Multi-tenant SSO and identity for regulated software. Tokens, organizations and sub-organizations, fine-grained permissions, and immediate notification when access changes, with tenant isolation, encryption at rest and a real audit trail built in rather than assembled by you.
It's built for teams who would have to explain a cross-tenant leak to a regulator.
Four things worth your attention
1. The audience check has no off switch.
const principal = verifier.verifyAuthorizationHeader(req.headers.authorization);
if (!principal.has("invoice.approve")) return res.status(403).end();
No variant of that call skips the audience. A token minted for another application is rejected, always. A machine token is refused where a user token is expected, and the reverse. The pattern is fail-closed: social login with no configured client id gets refused rather than trusted, and missing configuration raises an error instead of quietly allowing something.
2. Tenant isolation at two independent layers. Postgres row-level security through a dedicated non-superuser role, plus an application-layer tenant guard on every org-scoped endpoint. Neither one alone is the security boundary, so a bug in one doesn't become a breach.
Organizations are a materialized-path tree. You can model a health system → hospitals → departments and delegate administration down it with SELF and SUBTREE scopes. We verify the wall with an automated 165-check cross-org leak matrix instead of asserting it in a datasheet.
3. Your app finds out the moment access changes. Role assigned or revoked, permissions edited, account disabled or erased: HMAC-SHA256 signed, replay-protected, delivered at-least-once through a transactional outbox, so the event and the business change commit together or not at all. Editing a role also invalidates the current tokens of everyone holding it, so revoked access doesn't linger until expiry.
if (!verifyWebhook(rawBody, sig, ts, secret)) return res.status(400).end();
4. Migration that doesn't cost your users their passwords. Describe your whole estate in one payload that references records by your own ids. A dry-run reports every bad email, duplicate and broken link before a single row is written, then commit provisions it in one transaction. bcrypt hashes carry over verbatim, so those users keep the password they already have. The import is idempotent, so retries are safe.
Also in the box
TOTP MFA. Refresh-token rotation with reuse detection, where a replayed token gets treated as a compromise signal rather than an ordinary 401. AES-GCM encryption at rest with rotatable keys and a blind index so encrypted emails stay searchable. GDPR erasure that scrubs personal data while keeping internal ids stable. SDKs for Node, Python, PHP and Java/Spring, all enforcing the same defaults.
Live today. Public docs, published SDKs, and a sandbox tenant you can spin up without talking to anyone.
docs.latchvector.com · latchvector.com/services
Hit any of these problems yourself? I'd like to hear how you solved it. Responses are open.
Identity for software that gets audited.
Top comments (0)