🎤 Speaking at KubeCon EU 2026 Lessons Learned Orchestrating Multi-Tenant GPUs on OpenShift AI View Session
Luca Berton
Platform Engineering

Authentication chaos: why “just add auth” turns into AAA, RBAC, and pain

Luca Berton
#authentication#authorization#security#jwks#jwt#entra#oauth#rbac#platformengineering#saas

I used to think auth was a box you tick.

You wire up a login page, slap a JWT on the response, add a middleware, ship. And then you “do permissions later”.

And then reality hits: auth isn’t one thing. It’s three things (AAA), plus product decisions, plus infrastructure decisions, plus “how do I not accidentally let user A read user B’s data”.

This is a post about auth chaos: why building an app with security, authentication, and permissions is harder than it looks—especially when you mix modern identity providers (Entra/Okta/Auth0/etc.), self-managed tokens, and a permission model that will evolve.


The moment everything breaks: two token worlds collide

Here’s the kind of bug that makes you stare at the wall.

So you can sign in successfully… and then your own API rejects the token.

Even worse: different parts of the backend might validate tokens differently:

Result: random endpoints work, others fail, and debugging feels like chasing ghosts.

This is auth chaos in one screenshot: your system doesn’t have a single “token contract.” It has two.

Why this happens

Because identity is a stack of choices, and it’s easy to accidentally make two incompatible sets of choices:


AAA: Authentication, Authorization, Accounting (and why most apps only do A)

AAA is old terminology, but it’s still the best mental model for why “auth” expands.

1) Authentication: Who are you?

2) Authorization: What are you allowed to do?

This is where “it worked in dev” goes to die.

3) Accounting / Auditing: What happened?

The part everyone ignores until:

This means:

If you’re building a real product, you will eventually need all three. The only question is when, and how painful you make the migration.


RBAC: the comforting lie (and also the right starting point)

RBAC sounds like the solution because it’s easy to explain:

It’s a great MVP model… until you add scope.

Because authorization is rarely just “can user delete”. It’s:

can user delete this document in this workspace owned by this org while subscription is active?

RBAC alone doesn’t capture that. You need RBAC plus scoping.

The minimum viable RBAC that won’t ruin your future

If you want RBAC that scales, start with three explicit concepts:

Then make authorization checks look like:

Even if internally you still store roles as strings, force your code to think in these terms early.


The hidden hard part: “permissions” is a product decision, not a code decision

Most permission systems fail because they were built backward:

This is why permissioning feels like quicksand: it’s coupled to product reality.

A good permission model reflects:


A practical lesson from the JWKS vs local JWT mess: define your “auth contract”

Your backend needs one crisp answer to these questions:

Token contract checklist

  1. Who issues tokens?

    • your backend (self-managed JWT / sessions)
    • Entra (OIDC access tokens / ID tokens)
    • both (but explicitly supported)
  2. What token types are accepted at the API boundary?

    • access token only (recommended)
    • never accept ID tokens as API auth
    • define audiences/scopes clearly
  3. How does verification work?

    • local secret / local keypair
    • JWKS (issuer-based)
    • both, but routed by issuer/audience
  4. What happens on rotation and revocation?

    • JWKS rotates keys automatically (if you implement caching right)
    • local tokens need key rotation strategy
    • revocation requires sessions, short TTL, or token blacklist
  5. How do permissions flow into the request context?

    • roles in token claims? (fast, but stale when roles change)
    • roles loaded from DB? (slower, but always fresh)
    • hybrid approach? (token has identity; DB holds authorization truth)

The “two verifiers” fix (and why it’s a smell)

Your suggested fix is pragmatic:

Update authGuard.ts to try self-managed JWT verification first, then fall back to Entra JWKS verification.

That can work. But it’s also a warning sign: you now support two issuers and two trust models.

If you do this, make it explicit and safe:

A safer pattern is:

// Pseudocode: route by issuer, don't "guess"
const { iss } = decodeWithoutVerifying(token);

if (iss === "https://login.microsoftonline.com/<tenant>/v2.0") {
  return verifyViaJwks(token);
}

if (iss === "your-app") {
  return verifyViaLocalKey(token);
}

throw new Unauthorized("Unknown token issuer");

If you must support both during a migration, do it like a migration:

VScode Auth


Where apps really get hurt: role drift, stale claims, and “who changed what?”

Once you add RBAC, a bunch of non-obvious issues appear:

1) Stale authorization

If you embed roles in JWT claims, and a user gets demoted, their token might keep admin powers until it expires.

Solutions:

2) Multi-tenant leakage

The #1 authorization bug in SaaS is forgetting scope:

Your DB queries must always include tenant scope. Every time. No exceptions.

3) No audit trail

When permissions change, you need to answer:

This becomes urgent the first time something “weird” happens.


What I’d do for an MVP that still has a future

If you’re building a product that might become B2B later, here’s the sane path:

Phase 1: Ship without future-faking

Phase 2: Add enterprise as an add-on

Phase 3: Make it boring and correct


The takeaway: “auth” isn’t plumbing, it’s your product’s trust boundary

Authentication chaos happens when you treat identity like an implementation detail.

But in a real app:

So yes: building an app with security, authentication, and permissions is difficult—not because the libraries are hard, but because the decisions are coupled and the failure modes are subtle.

If you want to avoid chaos, don’t aim for “auth done.” Aim for:

← Back to Blog