Tools: Essential Guide: We Replaced API Keys with Tokens That Self-Destruct in 5 Minutes

Tools: Essential Guide: We Replaced API Keys with Tokens That Self-Destruct in 5 Minutes

The whole idea in two lines of HTTP

Honest comparison

A real scenario: GitHub Actions → your deploy API

How verification works — 14 steps, fail-closed

Gateway mode — protect services you can't modify

Drop it into your stack in ~5 lines

Try it in 3 commands

Honest pros and cons

- 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

Don't use OathMesh (yet) if

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

What's coming 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. Leaked? By the time an attacker tries it, it's already dead. This is the real picture — no spin: 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. 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. Fail-closed means: if any single step fails, the request is rejected immediately. No partial-valid state. No fallback. Just 401. Steps 6 and 13 are the heavy hitters. No algorithm confusion. No replay. No exceptions. Already have APIs you can't change? Run OathMesh as a reverse proxy in front of them. Your upstream gets clean, pre-verified identity headers. Zero code changes required. Full examples for Express, Flask, Django, and chi are in the quickstarts. Or run ./demo.sh for the full golden-path demo end to end. 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. A leaked credential in your environment would cause real damage You need tokens that live longer than 5 minutes by design You can't add infrastructure — the Issuer service is not optional 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 Built by Moustafa Mahmoud Atta & Abd El-Sabour Ashraf — MIT License Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

Authorization: Bearer abc123xyz_still_valid_since_2022 Authorization: Bearer abc123xyz_still_valid_since_2022 Authorization: Bearer abc123xyz_still_valid_since_2022 Authorization: OathMesh eyJhbGciOiJFZERTQSIsInR5cCI6Im9tK2p3dCJ9... └── expires in 300 seconds. enforced. not optional. Authorization: OathMesh eyJhbGciOiJFZERTQSIsInR5cCI6Im9tK2p3dCJ9... └── expires in 300 seconds. enforced. not optional. Authorization: OathMesh eyJhbGciOiJFZERTQSIsInR5cCI6Im9tK2p3dCJ9... └── expires in 300 seconds. enforced. not optional. 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 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 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 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} 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} 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} 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 }); } 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 }); } 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 }); } 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 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 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 npm install @oathmesh/sdk pip install oathmesh go install github.com/oathmesh/oathmesh/cmd/oathmesh@latest npm install @oathmesh/sdk pip install oathmesh go install github.com/oathmesh/oathmesh/cmd/oathmesh@latest npm install @oathmesh/sdk pip install oathmesh go install github.com/oathmesh/oathmesh/cmd/oathmesh@latest - 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. - 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