DEV Community

Cover image for We Replaced API Keys with Tokens That Self-Destruct in 5 Minutes
Mustafa Mahmoud Atta
Mustafa Mahmoud Atta

Posted on

We Replaced API Keys with Tokens That Self-Destruct in 5 Minutes

Your CI job has a secret. It's been sitting in your environment variables for two years. You don't know exactly who has access to it. Rotating it means coordinating three teams. So you don't.

That's not a process failure. That's what API keys are designed to be: long-lived strings that survive forever because they have to.

We're Moustafa Mahmoud Atta and Abd El-Sabour Ashraf, and we built OathMesh to change that default.

Every machine call gets a token that's cryptographically signed, scoped to a single action, and dead in ≤ 5 minutes.


The whole idea in two lines of HTTP

Before:

Authorization: Bearer abc123xyz_still_valid_since_2022
Enter fullscreen mode Exit fullscreen mode

After:

Authorization: OathMesh eyJhbGciOiJFZERTQSIsInR5cCI6Im9tK2p3dCJ9...
                        └── expires in 300 seconds. enforced. not optional.
Enter fullscreen mode Exit fullscreen mode

Leaked? By the time an attacker tries it, it's already dead.


Honest comparison

This is the real picture — no spin:

Feature API Keys Typical JWT Short-lived JWT + jti OathMesh
Lifetime ♾️ Forever Hours–Days 5–15 min (if configured) ⏱️ ≤ 300s. Enforced.
Cryptography ❌ Just a string HS256 / RS256 RS256 / ES256 Ed25519 only
Replay protection ✅ jti blocklist (DIY) Built-in, required
Action scoping ⚠️ Custom, optional ⚠️ Custom, optional act required
Policy engine Pkl rules, hot-reload
Audit log ⚠️ Roll your own ⚠️ Roll your own Every allow & deny, NDJSON
Leaked secret blast radius 💀 Forever 🩸 Hours 🟡 Minutes 🟢 ≤ 5 min. Max.
Rotation 😰 Manual + coordination ⚠️ Varies ⚠️ Varies Auto-expiry by design

Honest take: You can get close to OathMesh with short-lived JWTs + a jti blocklist. What OathMesh adds is the opinionated wrapper: TTL enforcement you can't disable, act scoping that's required (not optional), a built-in policy engine, and a full audit trail — out of the box, not DIY.

Already running SPIFFE/SPIRE or cloud workload identity (AWS IRSA, GCP WI)? Great — those are excellent fits for Kubernetes-native setups. OathMesh is for teams who want this security model without the full service-mesh footprint. And if you want simpler than all of this, keep the API key. If you want safer, read on.


A real scenario: GitHub Actions → your deploy API

image

The CI job never stores a secret. It requests a token with a 300-second TTL, uses it, and it's gone. Even if someone captures it from your logs — they get nothing.


How verification works — 14 steps, fail-closed

Fail-closed means: if any single step fails, the request is rejected immediately. No partial-valid state. No fallback. Just 401.

image

Step What it checks
1 Valid JWS compact structure (3 segments)
2 Header: typ = om+jwt, alg = EdDSA
3 Payload decoded, iss extracted
4 iss in trusted issuer list
5 JWKS loaded (cached in-memory)
6 Ed25519 signature verified — no algorithm confusion possible
7 iss re-verified post-signature
8 exp is in the future (±10s clock skew tolerance)
9 iat is not in the future
10 aud matches exactly
11 All required claims present: sub, act, jti
12 Request hash binding checked (if present)
13 jti checked against replay cache — seen before → rejected
14 Policy evaluated → audit event emitted

Steps 6 and 13 are the heavy hitters. No algorithm confusion. No replay. No exceptions.


Gateway mode — protect services you can't modify

Already have APIs you can't change? Run OathMesh as a reverse proxy in front of them.

image

Your upstream gets clean, pre-verified identity headers. Zero code changes required.


Drop it into your stack in ~5 lines

Go (chi)

r.Use(middleware.OathMeshMiddleware(cfg))

// Fully typed caller context in your handler:
caller := middleware.CallerFrom(r.Context())
// caller.Principal.Subject → "agent://ci/deploy-bot"
// caller.Action            → "deploy"
// caller.TokenID           → unique jti for this call
Enter fullscreen mode Exit fullscreen mode

Python (FastAPI)

from oathmesh import verify_token, OathMeshError

@app.post("/deploy")
async def deploy(request: Request):
    try:
        caller = verify_token(request.headers["authorization"], config)
    except OathMeshError as e:
        raise HTTPException(status_code=401, detail=str(e))
    return {"deployed_by": caller.principal.subject}
Enter fullscreen mode Exit fullscreen mode

Next.js (App Router)

import { withOathMesh } from '@oathmesh/oathmesh/next';

const oathmesh = withOathMesh({ audience, trustedIssuers });

export async function POST(request: NextRequest) {
  const { caller, error } = await oathmesh(request);
  if (error) return error; // typed 401 — missing, invalid, expired, replayed
  return NextResponse.json({ subject: caller.principal.subject });
}
Enter fullscreen mode Exit fullscreen mode

Full examples for Express, Flask, Django, and chi are in the quickstarts.


Try it in 3 commands

git clone https://github.com/oathmesh/oathmesh.git && cd oathmesh
docker-compose up -d

# Mint a token (300s = the maximum, enforced by the issuer)
TOKEN=$(docker compose exec oathmesh ./bin/oathmesh mint \
  --sub "agent://repo/acme/deploy-bot" \
  --aud "https://inventory.internal" \
  --act "deploy" \
  --ttl 300 \
  --quiet)

curl -H "Authorization: OathMesh $TOKEN" http://localhost:8081/inventory
Enter fullscreen mode Exit fullscreen mode

Or run ./demo.sh for the full golden-path demo end to end.


Honest pros and cons

What we got right ✅

  • 300s max TTL is enforced in the issuer — there's no config flag to make it longer. Intentional, not an oversight.
  • Ed25519 only — one algorithm, the correct one. Algorithm confusion attacks aren't possible.
  • Fail-closed verification — all 14 steps must pass. No partial-valid state.
  • Full audit trail — every allow and every deny logged as NDJSON. grep-able. Cloud-native.
  • Gateway mode — zero changes to your existing APIs.
  • MIT license — take it, fork it, self-host it.
    What we know needs work ❌

  • It's v0.1.0 — rough edges exist. Read the threat model before running in production.

  • You need to run an Issuer service — one more thing to deploy and keep alive. Real operational cost.

  • Horizontal scaling needs Redis — the replay cache is in-memory by default. Multiple instances need a shared Redis. We're not hiding this.

  • Pkl for policies — powerful, but not everyone knows Apple Pkl. A visual editor is on the roadmap.

  • Machine-to-machine only — user auth is a different problem. Use OAuth2/OIDC for that.

- Your Ed25519 private key is your trust root — unlike an API key (which compromises one service), a leaked signing key compromises every service on your mesh. Store it in a secrets manager (Vault, AWS KMS, GCP Secret Manager) — not in an env var. This is the one rule that matters most.

Use OathMesh if

  • You're running CI/CD pipelines that call internal APIs
  • You have service-to-service calls in a zero-trust or service mesh setup
  • You're building AI agents that call protected services
  • A leaked credential in your environment would cause real damage

    Don't use OathMesh (yet) if

  • You need tokens that live longer than 5 minutes by design

  • You can't add infrastructure — the Issuer service is not optional

- You need user-facing authentication — this is not the tool

What's coming

Feature Status
Rust + Java SDKs 🔜 Next release (v0.2.0)
mTLS + rate limiting in Gateway 🗓️ Planned
Visual policy editor (no Pkl required) 🗓️ Planned
Audit dashboard 🗓️ Planned
GitLab CI + GitHub App issuers 🗓️ Planned

We built OathMesh because we kept hitting the same wall: leaked credentials with no expiry and no audit trail. The fix shouldn't require a security team or an enterprise budget.

It's early. It has rough edges. But the model is sound, the code is open, and the MIT license means you can take it wherever you need it.

If it solves a problem you have — or if you think we're wrong about something — open an issue or start a discussion. We genuinely want to hear from you.

🔗 github.com/oathmesh/oathmesh

npm install @oathmesh/sdk
pip install oathmesh
go install github.com/oathmesh/oathmesh/cmd/oathmesh@latest
Enter fullscreen mode Exit fullscreen mode

Built by Moustafa Mahmoud Atta & Abd El-Sabour Ashraf — MIT License

Top comments (0)