@engjts/auth
v1.2.0
Published
Janus Token System (JTS) - A two-component authentication architecture for secure, revocable, and confidential API authentication
Downloads
17
Readme
@engjts/auth
Janus Token System (JTS) SDK for TypeScript/Node.js
A two-component authentication architecture for secure, revocable, and confidential API authentication.
🌟 Features
- Three Security Profiles: JTS-L (Lite), JTS-S (Standard), JTS-C (Confidentiality)
- Stateless Verification: Fast BearerPass verification using asymmetric cryptography
- Instant Revocation: StateProof-based session management with database backing
- Replay Detection: Automatic detection and prevention of token reuse (JTS-S/C)
- Device Binding: Optional device fingerprint validation
- Express Integration: Ready-to-use middleware for Express.js
- Multiple Storage Backends: In-memory, Redis, and PostgreSQL adapters
- CLI Tools: Key generation, token inspection, and configuration utilities
- Zero External JWT Dependencies: Full cryptographic control using native Node.js crypto
📦 Installation
npm install @engjts/authFor CLI tools globally:
npm install -g @engjts/auth🚀 Quick Start
1. Setup Auth Server
import {JTSAuthServer, generateKeyPair} from '@engjts/auth';
// Generate signing key
const signingKey = await generateKeyPair('my-key-2025', 'RS256');
// Create auth server
const authServer = new JTSAuthServer({
profile: 'JTS-S/v1',
signingKey,
bearerPassLifetime: 300, // 5 minutes
stateProofLifetime: 604800, // 7 days
});
// Login user
const tokens = await authServer.login({
prn: 'user-123',
permissions: ['read:profile', 'write:posts'],
});
console.log(tokens.bearerPass); // Use in Authorization header
console.log(tokens.stateProof); // Store in HttpOnly cookie2. Setup Resource Server
import {JTSResourceServer} from '@engjts/auth';
const resourceServer = new JTSResourceServer({
publicKeys: [signingKey],
audience: 'https://api.example.com',
});
// Verify token
const result = await resourceServer.verify(bearerPass);
if (result.valid) {
console.log('User:', result.payload.prn);
console.log('Permissions:', result.payload.perm);
}3. Express Integration
import express from 'express';
import {jtsAuth, jtsRequirePermissions, createJTSRoutes} from '@engjts/auth';
const app = express();
// Create routes
const routes = createJTSRoutes({
authServer,
validateCredentials: async (req) => {
const {email, password} = req.body;
// Validate credentials...
return {prn: email, permissions: ['read:profile']};
},
});
// Mount auth endpoints
app.post('/jts/login', routes.loginHandler);
app.post('/jts/renew', routes.renewHandler);
app.post('/jts/logout', routes.logoutHandler);
// Protected routes
app.get('/api/profile',
jtsAuth({resourceServer}),
(req, res) => {
res.json({user: req.jts.payload.prn});
}
);
// Permission-protected routes
app.get('/api/admin',
jtsAuth({resourceServer}),
jtsRequirePermissions({required: ['admin:access']}),
(req, res) => {
res.json({message: 'Admin area'});
}
);🔧 CLI Tools
The jts CLI provides utilities for key management and token inspection.
Installation
# Global installation
npm install -g @engjts/auth
# Or use with npx
npx @engjts/auth jts --helpCommands
jts keygen - Generate Key Pairs
# Generate ES256 key pair (recommended)
jts keygen -a ES256 -o signing-key.pem
# Generate RS256 key with 4096 bits
jts keygen -a RS256 --bits 4096 -o rsa-key.pem
# Output as JWK format
jts keygen -a ES256 -f jwk -o signing-key.json
# Specify custom key ID
jts keygen -a ES256 --kid my-key-2025-001 -o key.pemOptions:
-a, --algorithm <alg>- Algorithm (RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512)-k, --kid <kid>- Key ID (auto-generated if not specified)-b, --bits <bits>- RSA key size in bits (default: 2048)-o, --output <file>- Output file for private key-p, --public-out <file>- Output file for public key-f, --format <format>- Output format:pemorjwk
jts inspect - Decode Token Contents
# Inspect a token
jts inspect eyJhbGciOiJFUzI1NiIsInR5cCI6Ikp...
# Inspect from file
jts inspect ./token.jwt
# Output as JSON
jts inspect <token> --jsonOutput includes:
- Header (algorithm, type, key ID)
- Payload (principal, permissions, expiration)
- Timestamp information
- Expiration status
jts verify - Verify Token Signature
# Verify with public key file (PEM)
jts verify <token> --key public-key.pem
# Verify with JWK file
jts verify <token> --key public-key.json
# Verify with remote JWKS
jts verify <token> --jwks https://auth.example.com/.well-known/jwks.json
# Verify with local JWKS file
jts verify <token> --jwks ./jwks.jsonOptions:
-k, --key <file>- Public key file (PEM or JWK)--jwks <url-or-file>- JWKS URL or file path
jts jwks - Convert to JWKS Format
# Convert single PEM to JWKS
jts jwks public-key.pem -o jwks.json
# Convert multiple keys
jts jwks key1.pem key2.pem key3.pem -o jwks.json
# Specify key ID for PEM files
jts jwks public-key.pem --kid my-key-2025Options:
-o, --output <file>- Output file-k, --kid <kid>- Key ID for PEM files
jts init - Initialize JTS Configuration
# Initialize with JTS-S profile (recommended for production)
jts init --profile JTS-S --algorithm ES256 --output ./config
# Initialize JTS-C profile (with encryption)
jts init --profile JTS-C --algorithm RS256 --output ./config
# Force overwrite existing
jts init --profile JTS-S -o ./config --forceOptions:
--profile <profile>- JTS profile: JTS-L, JTS-S, or JTS-C-a, --algorithm <alg>- Signing algorithm-o, --output <dir>- Output directory-f, --force- Overwrite existing directory
Generated files:
jts.config.json- Configuration filesigning-key.pem- Private signing keysigning-key.pub.pem- Public signing keyjwks.json- JWKS public keysexample.ts- Example usage code.gitignore- Prevents committing private keys
📊 Profiles Comparison
| Feature | JTS-L (Lite) | JTS-S (Standard) | JTS-C (Confidentiality) | |---------------------|---------------------|------------------|-------------------------| | Use Case | MVP, Internal Tools | Production Apps | Fintech, Healthcare | | StateProof Rotation | ❌ Optional | ✅ Required | ✅ Required | | Replay Detection | ❌ No | ✅ Yes | ✅ Yes | | Device Binding | ❌ No | ✅ Optional | ✅ Optional | | Payload Encryption | ❌ No | ❌ No | ✅ Yes (JWE) | | Complexity | Low | Medium | High |
🔧 Storage Adapters
In-Memory (Development)
import {InMemorySessionStore} from '@engjts/auth';
const store = new InMemorySessionStore();
const authServer = new JTSAuthServer({
sessionStore: store,
// ...other options
});Redis (Production)
import {RedisSessionStore} from '@engjts/auth';
import Redis from 'ioredis';
const redis = new Redis();
const store = new RedisSessionStore({
client: redis,
keyPrefix: 'jts:',
});
const authServer = new JTSAuthServer({
sessionStore: store,
// ...other options
});PostgreSQL (Production)
import {PostgresSessionStore} from '@engjts/auth';
import {Pool} from 'pg';
const pool = new Pool({connectionString: process.env.DATABASE_URL});
const store = new PostgresSessionStore({
pool,
tableName: 'jts_sessions',
});
// Initialize table (run once)
await store.initialize();
const authServer = new JTSAuthServer({
sessionStore: store,
// ...other options
});🔑 Key Management
Key Generation
import {generateKeyPair, generateRSAKeyPair, generateECKeyPair} from '@engjts/auth';
// Auto-select based on algorithm
const key = await generateKeyPair('key-id', 'RS256');
// RSA specific
const rsaKey = await generateRSAKeyPair('rsa-key', 'RS256', 2048);
// EC specific (recommended for performance)
const ecKey = await generateECKeyPair('ec-key', 'ES256');Key Rotation
// Generate new key
const newKey = await generateKeyPair('key-2025-002', 'RS256');
// Rotate (old key becomes "previous", new key becomes "current")
authServer.rotateSigningKey(newKey);
// Old tokens remain valid until they expireJWKS Endpoint
// Get JWKS for distribution
const jwks = authServer.getJWKS();
// {
// keys: [
// { kty: 'RSA', kid: 'key-2025-002', use: 'sig', ... },
// { kty: 'RSA', kid: 'key-2025-001', use: 'sig', ... }
// ]
// }🛡️ Security Features
CSRF Protection
All mutating endpoints require X-JTS-Request: 1 header:
fetch('/jts/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-JTS-Request': '1', // Required for CSRF protection
},
body: JSON.stringify(credentials),
});Device Fingerprint
import {createDeviceFingerprint} from '@engjts/auth';
const fingerprint = createDeviceFingerprint({
userAgent: navigator.userAgent,
screenResolution: `${screen.width}x${screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});
// Include in login
const tokens = await authServer.login({
prn: 'user-123',
deviceFingerprint: fingerprint,
});Grace Period
Handle in-flight requests during token expiry:
const resourceServer = new JTSResourceServer({
gracePeriodTolerance: 30, // Accept tokens up to 30s after expiry
});📱 Client SDK
import {JTSClient} from '@engjts/auth';
const client = new JTSClient({
authServerUrl: 'https://auth.example.com',
autoRenewBefore: 60, // Renew 1 minute before expiry
});
// Login
const result = await client.login({
email: '[email protected]',
password: 'password123',
});
// Auto-refreshing fetch
const response = await client.fetch('https://api.example.com/profile');
// Event handlers
client.onRefresh((token) => console.log('Token refreshed'));
client.onExpired(() => console.log('Session expired'));
// Logout
await client.logout();🔴 Error Handling
import {JTSError} from '@engjts/auth';
try {
const result = await resourceServer.verify(token);
} catch (error) {
if (error instanceof JTSError) {
console.log(error.errorCode); // 'JTS-401-01'
console.log(error.errorKey); // 'bearer_expired'
console.log(error.action); // 'renew'
console.log(error.httpStatus); // 401
// Send standard error response
res.status(error.httpStatus).json(error.toJSON());
}
}Error Codes
| Code | Key | Description | Action | |------------|---------------------|-------------------------------|--------| | JTS-400-01 | malformed_token | Token cannot be parsed | reauth | | JTS-400-02 | missing_claims | Required claims missing | reauth | | JTS-401-01 | bearer_expired | BearerPass has expired | renew | | JTS-401-02 | signature_invalid | Signature verification failed | reauth | | JTS-401-03 | stateproof_invalid | StateProof not found/invalid | reauth | | JTS-401-04 | session_terminated | Session ended (logout) | reauth | | JTS-401-05 | session_compromised | Replay attack detected | reauth | | JTS-401-06 | device_mismatch | Device fingerprint mismatch | reauth | | JTS-403-01 | audience_mismatch | Wrong audience | none | | JTS-403-02 | permission_denied | Missing permissions | none | | JTS-403-03 | org_mismatch | Wrong organization | none | | JTS-500-01 | key_unavailable | Public key not found | retry |
📄 API Reference
Core Exports
Crypto
generateKeyPair(kid, algorithm)- Generate signing key pairgenerateRSAKeyPair(kid, algorithm, modulusLength)- Generate RSA key pairgenerateECKeyPair(kid, algorithm)- Generate EC key pairsign(data, privateKey, algorithm)- Sign dataverify(data, signature, publicKey, algorithm)- Verify signaturepemToJwk(pem, kid, algorithm)- Convert PEM to JWKjwkToPem(jwk)- Convert JWK to PEMkeyPairToJwks(keyPairs)- Convert key pairs to JWKS
Tokens
createBearerPass(options)- Create a BearerPass tokenverifyBearerPass(token, options)- Verify and decode BearerPassdecodeBearerPass(token)- Decode without verificationisTokenExpired(payload)- Check if token is expiredhasPermission(payload, permission)- Check single permissionhasAllPermissions(payload, permissions)- Check all permissionshasAnyPermission(payload, permissions)- Check any permission
JWE (JTS-C)
createEncryptedBearerPass(options)- Create encrypted JWE tokendecryptJWE(jwe, options)- Decrypt JWE tokenverifyEncryptedBearerPass(jwe, options)- Decrypt and verifyisEncryptedToken(token)- Check if token is JWE
Server
JTSAuthServer- Authentication server classJTSResourceServer- Resource server class
Client
JTSClient- Client SDK classInMemoryTokenStorage- Simple token storage
Middleware
jtsAuth(options)- Authentication middlewarejtsOptionalAuth(options)- Optional auth middlewarejtsRequirePermissions(options)- Permission middlewarecreateJTSRoutes(options)- Create route handlersmountJTSRoutes(app, options)- Mount routes on Express app
Stores
InMemorySessionStore- In-memory session storeRedisSessionStore- Redis-backed session storePostgresSessionStore- PostgreSQL-backed session store
JTS Claims
| Claim | Name | Description |
|----------|--------------------|-----------------------------|
| prn | Principal | User/entity identifier |
| aid | Anchor ID | Links to session record |
| tkn_id | Token ID | Unique token identifier |
| exp | Expiration | Token expiry timestamp |
| iat | Issued At | Token creation timestamp |
| aud | Audience | Intended recipient |
| dfp | Device Fingerprint | Device binding hash |
| perm | Permissions | Array of permission strings |
| grc | Grace Period | Expiry tolerance (seconds) |
| org | Organization | Tenant identifier |
| atm | Auth Method | How user authenticated |
| spl | Session Policy | Concurrent session policy |
�� Running the Example
# Clone and install
git clone https://github.com/ukungzulfah/jts-core.git
cd jts-core
npm install
# Run example server
npm run example
# Test with curl
curl -X POST http://localhost:3000/jts/login \
-H "Content-Type: application/json" \
-H "X-JTS-Request: 1" \
-d '{"email":"[email protected]","password":"password123"}'🧪 Testing
# Run all tests
npm test
# Run with watch mode
npm run test:watch
# Run with coverage
npm run test:coverage📖 Specification
This SDK implements the Janus Token System (JTS) Specification v1.1.
See JTS_Specification_v1.md for the full specification.
🤝 Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
📜 License
MIT License - see LICENSE file for details.
