cit-auth
v7.1.0
Published
A cryptographic intent-based authentication library for Node.js and Express that replaces JWTs with non-bearer, replay-resistant proofs.
Maintainers
Readme
🔐 CIT Auth (cit-auth)
A cryptographic intent-based authentication library for Node.js and Express
No JWTs. No bearer tokens. No replay attacks.
Updates
- Version 7.1.0 is available, this version uses .env files unlike 7.0.0 which was hardcoded values in .json file
- Example updated with usage of .env load configuration
Installation
npm install cit-authNode.js 18+ required.
One-time Setup
Before using
cit-auth, always initialize your identity:
npx cit-auth initThis will generate a .env file file if not already existing with your Ed25519 key pair.
Why cit-auth exists
JWTs, API keys, and session tokens all share the same fundamental flaw:
Whoever holds the token is trusted.
This causes:
- Token theft
- Replay attacks
- Credential leaks
- Long-lived secrets
- Broken zero-trust models
cit-auth replaces bearer authentication with cryptographic proof of intent.
What is Cryptographic Intent Authentication?
Instead of sending a reusable token, the client proves:
- Who it is (public-key identity)
- What it wants to do (intent)
- When it wants to do it (timestamp)
- Why it is allowed (context)
- Right now (server-issued challenge)
Every request is individually authorized and single-use.
Key Properties
✅ No bearer tokens
✅ No shared secrets
✅ No refresh tokens
✅ Replay-resistant (single-use challenges)
✅ Intent-bound authorization
✅ Zero-trust friendly
✅ Express middleware included
How it Works (High Level)
- Server issues a challenge
- Client creates an intent
- Client signs intent + challenge using Ed25519
- Server verifies cryptographic proof
- Request is accepted or rejected
The proof is single-use, expires immediately, and cannot be reused.
Quick Example
Server (Express)
import express from "express";
import bodyParser from "body-parser";
import dotenv from "dotenv";
import { loadIdentity, citAuth, issueChallenge } from "cit-auth";
// Load .env into process.env
dotenv.config();
const app = express();
app.use(bodyParser.json());
// 🔐 Load public key from environment
const { publicKey } = loadIdentity();
// Endpoint to issue a challenge
app.get("/challenge", (_, res) => {
const challenge = issueChallenge();
res.json({ challenge });
});
// Secure endpoints middleware
const secureMiddleware = citAuth({
getPublicKey: () => publicKey
});
// Endpoint 1: /secure/profile
app.post("/secure/profile", secureMiddleware, (req, res) => {
res.json({
ok: true,
data: { name: "John Doe" }
});
});
// Endpoint 2: /secure/contact
app.post("/secure/contact", secureMiddleware, (req, res) => {
res.json({
ok: true,
data: { email: "[email protected]" }
});
});
// Public endpoint
app.get("/", (_, res) => res.send("Public endpoint accessible by anyone"));
app.listen(3000, () =>
console.log("Server running on http://localhost:3000")
);
Client
import fetch from "node-fetch";
import dotenv from "dotenv";
import { loadIdentity, createIntent, signIntent } from "cit-auth";
// Load .env
dotenv.config();
// 🔐 Load private key from environment
const { privateKey } = loadIdentity();
// Helper to request challenge and sign intent
async function signedRequest(endpoint, action, resource) {
const { challenge } = await fetch("http://localhost:3000/challenge")
.then(r => r.json());
const intent = createIntent(action, resource);
const signature = signIntent(privateKey, intent, challenge);
const res = await fetch(`http://localhost:3000${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ intent, challenge, signature })
});
console.log(`Response from ${endpoint}:`, await res.json());
}
// 1️⃣ Authorized requests
await signedRequest("/secure/profile", "read", "profile");
await signedRequest("/secure/contact", "read", "contact");
// 2️⃣ Unauthorized request (no signature)
const res = await fetch("http://localhost:3000/secure/profile", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({})
});
console.log("Unauthorized response:", await res.json());
Behavior
- Authorized requests succeed and return sensitive data.
- Unauthorized requests fail with 403 — no sensitive data leaked.
What Makes This Different from JWT?
| Feature | JWT | cit-auth |
|--------|-----|------------|
| Bearer token | ✅ | ❌ |
| Replay resistant | ❌ | ✅ |
| Intent-bound | ❌ | ✅ |
| Per-request auth | ❌ | ✅ |
| Secret leakage risk | High | Near zero |
| Zero-trust friendly | ❌ | ✅ |
JWT asks:
“Is this token valid?”
CIT asks:
“Is this action valid, right now, from this identity, for this purpose?”
Identity Model
- Uses Ed25519 public-key cryptography
- Private keys never leave the client
- Servers store public keys only
- No secret rotation required (long-lived key)
Security Model
- Challenge–response prevents replay
- Timestamp checks prevent delayed use
- Nonce enforcement prevents duplication
- Intent binding prevents privilege escalation
Even if an attacker intercepts a request:
- It cannot be reused
- It cannot be modified
- It expires immediately
When You Should Use cit-auth
✔ APIs with high security requirements
✔ Zero-trust systems
✔ Internal service-to-service auth
✔ CI/CD or machine authentication
✔ Replacing JWTs or API keys
When You Should NOT Use It
❌ Simple session cookies for basic apps
❌ Legacy systems that require bearer tokens
❌ Browsers without crypto support
Roadmap
- 🔐 Replay cache adapters (Redis / memory)
- 🧠 Policy engine (fine-grained intent rules)
- 🌐 WebAuthn support
- 🧾 Audit-friendly intent logs
- 📦 NuGet (.NET) implementation
- 📜 RFC-style specification
Philosophy
Security should not depend on hiding secrets.
It should depend on proving intent.
License
MIT © Ethern Myth

