FluxProtocol solves the fundamental security limitations of JWT:
| Feature | JWT | FluxProtocol |
|---|---|---|
| Device Binding | ❌ | ✅ Cryptographic proof per device |
| Token Replay | High risk | ✅ Zero risk (proof required) |
| Instant Revocation | Impossible | ✅ Server-controlled sessions |
| Multi-device | Weak | ✅ Per-device session tracking |
| Token Theft | Valid until expiry | ✅ Invalid immediately |
| Rotation | Manual | ✅ Automatic (20-60s) |
npm install @flux-protocol/sdkyarn add @flux-protocol/sdkpnpm add @flux-protocol/sdkimport { FluxProtocol } from '@flux-protocol/sdk';
// 1. Initialize (with optional storage)
const flux = FluxProtocol();
// Or with persistent storage:
// import { LocalStorageAdapter } from '@flux-protocol/sdk';
// const flux = FluxProtocol({ storage: new LocalStorageAdapter() });
// 2. Login (your endpoint returns tokens)
const { accessToken, proofKey, expiresIn } = await fetch('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password })
}).then(r => r.json());
flux.setTokens({ accessToken, proofKey, expiresIn });
// 3. Make authenticated requests
const headers = await flux.buildHeadersAsync();
await fetch('/api/data', { headers });
// 4. Handle automatic rotation
flux.onRotate(async () => {
const { accessToken, expiresIn } = await fetch('/auth/rotate', {
method: 'POST',
headers: await flux.buildHeadersAsync()
}).then(r => r.json());
flux.updateAccess(accessToken, expiresIn);
});
// 5. Logout
await fetch('/auth/logout', { method: 'POST', headers: await flux.buildHeadersAsync() });
flux.stop();FluxProtocol SDK supports multiple storage adapters for token persistence:
In-memory storage with no persistence.
import { FluxProtocol, MemoryStorageAdapter } from '@flux-protocol/sdk';
const flux = FluxProtocol();
// or explicitly:
const flux = FluxProtocol({ storage: new MemoryStorageAdapter() });Use Cases:
- Testing and development
- Short-lived sessions
- Server-side rendering (per-request)
Pros: Fast, lightweight, no dependencies
Cons: Data lost on refresh/restart
Persistent browser storage using localStorage.
import { FluxProtocol, LocalStorageAdapter } from '@flux-protocol/sdk';
const flux = FluxProtocol({
storage: new LocalStorageAdapter({
prefix: 'myapp_' // Optional: default is 'flux_'
})
});Use Cases:
- Web applications
- Progressive Web Apps (PWAs)
- Single-page applications
Pros: Persistent across sessions, ~5-10MB storage
Cons: Synchronous API, per-domain limit
Fallback: Memory storage if localStorage unavailable
Cookie-based storage with security options.
import { FluxProtocol, SecureCookiesAdapter } from '@flux-protocol/sdk';
const flux = FluxProtocol({
storage: new SecureCookiesAdapter({
secure: true, // HTTPS only (default: true)
sameSite: 'Strict', // CSRF protection (default: 'Lax')
maxAge: 30, // Days (default: 7)
path: '/', // Cookie path (default: '/')
domain: '.example.com', // Optional: cookie domain
prefix: 'flux_' // Cookie name prefix (default: 'flux_')
})
});Use Cases:
- Server-side rendering (Next.js, Remix)
- Multi-domain authentication
- Mobile web apps
Pros: Sent with every request, SSR-compatible
Cons: ~4KB per cookie limit, sent on every request
Fallback: Memory storage if cookies unavailable
Asynchronous browser database for large storage capacity.
import { FluxProtocol, IndexedDBAdapter } from '@flux-protocol/sdk';
const flux = FluxProtocol({
storage: new IndexedDBAdapter({
dbName: 'MyAppDB', // Database name (default: 'FluxProtocolDB')
storeName: 'auth_tokens', // Store name (default: 'tokens')
version: 1 // DB version (default: 1)
})
});Use Cases:
- Progressive Web Apps (PWAs)
- Offline-first applications
- Large token payloads
Pros: Async operations, ~50MB+ storage, best for PWAs
Cons: More complex API, initialization overhead
Fallback: Memory storage if IndexedDB unavailable
Create your own adapter by implementing the StorageAdapter interface:
import { StorageAdapter } from '@flux-protocol/sdk';
class RedisStorageAdapter implements StorageAdapter {
async set(key: string, value: string): Promise<void> {
await redis.set(key, value);
}
async get(key: string): Promise<string | null> {
return await redis.get(key);
}
async remove(key: string): Promise<void> {
await redis.del(key);
}
async clear(): Promise<void> {
const keys = await redis.keys('flux_*');
if (keys.length > 0) await redis.del(...keys);
}
}
const flux = FluxProtocol({ storage: new RedisStorageAdapter() });Use Cases:
- Server-side Node.js applications
- Distributed systems (Redis, Memcached)
- Custom database backends (SQLite, PostgreSQL)
✅ Automatic Persistence - Tokens saved automatically on setTokens() and updateAccess()
✅ Automatic Restoration - Tokens restored on SDK initialization
✅ Graceful Fallbacks - Falls back to memory if storage unavailable
✅ No Manual Management - No need to handle localStorage/cookies manually
✅ Cross-Tab Sync - Works across browser tabs (except memory storage)
✅ Secure by Default - All adapters include security best practices
Each device gets a unique deviceId and proofKey (256-bit secret). This ensures tokens are bound to specific devices.
const flux = FluxProtocol({ deviceId: 'my-device-123' });
// or auto-generate
const flux = FluxProtocol(); // generates secure random deviceIdEvery request includes a signed proof to prevent replay attacks:
Authorization: Flux AT.sessionId.deviceId.timestamp.expiry.signature
X-Flux-Device: deviceId
X-Flux-Proof: timestamp.signatureThe proof signature is: HMAC-SHA256(deviceId|timestamp|accessToken, proofKey)
Tokens rotate every 20-60 seconds (configurable). The SDK handles this automatically.
flux.onRotate(async (event) => {
console.log('Rotating at:', new Date(event.expiresAt));
// Call your refresh endpoint
});All session state lives on the server (Redis recommended). Instant revocation:
// Logout device
DELETE /sessions/{deviceId}
// Logout all devices
DELETE /sessions/user/{userId}Initialize SDK with authentication tokens.
flux.setTokens({
accessToken: string; // AT.sessionId.deviceId.timestamp.expiry.sig
proofKey: string; // PK.base64-encoded-256-bit-key
expiresIn: number; // Milliseconds until expiry
});Generate HTTP headers for requests (async).
const headers = await flux.buildHeadersAsync();
// {
// Authorization: 'Flux AT....',
// 'X-Flux-Device': 'deviceId',
// 'X-Flux-Proof': 'timestamp.signature'
// }Update access token after rotation.
flux.updateAccess(newAccessToken, newExpiresIn);Register a callback for rotation events.
flux.onRotate(async (event: RotateEvent) => {
// event.timestamp, event.currentToken, event.expiresAt
await refreshToken();
});Register an error handler.
flux.onError((event: ErrorEvent) => {
console.error(`[${event.code}]: ${event.message}`);
});Stop the client and clear all timers/tokens.
flux.stop();flux.isActive(): boolean // Check if session is active
flux.getExpiresIn(): number // Get remaining time (ms)
flux.getDeviceId(): string // Get device ID
flux.getAccessToken(): string | null // Get current access token
flux.decode(token): AccessTokenPayload // Decode a token
flux.verify(token, key): Promise<boolean> // Verify token signatureimport axios from 'axios';
import { FluxProtocol } from '@flux-protocol/sdk';
const flux = FluxProtocol();
const api = axios.create({ baseURL: 'https://api.example.com' });
// Request interceptor
api.interceptors.request.use(async (config) => {
if (flux.isActive()) {
const headers = await flux.buildHeadersAsync();
config.headers = { ...config.headers, ...headers };
}
return config;
});
// Response interceptor (handle 401)
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !error.config._retry) {
error.config._retry = true;
const { accessToken, expiresIn } = await api.post('/auth/rotate');
flux.updateAccess(accessToken, expiresIn);
return api(error.config);
}
return Promise.reject(error);
}
);import { FluxProtocol } from '@flux-protocol/sdk';
import { createContext, useContext, useState, useEffect } from 'react';
const FluxContext = createContext(null);
export function FluxAuthProvider({ children }) {
const [flux] = useState(() => FluxProtocol());
useEffect(() => {
flux.onRotate(async () => {
const { accessToken, expiresIn } = await fetch('/auth/rotate', {
method: 'POST',
headers: await flux.buildHeadersAsync()
}).then(r => r.json());
flux.updateAccess(accessToken, expiresIn);
});
}, [flux]);
return <FluxContext.Provider value={flux}>{children}</FluxContext.Provider>;
}
export const useFlux = () => useContext(FluxContext);async function authenticatedFetch(url: string, options: RequestInit = {}) {
const headers = await flux.buildHeadersAsync();
return fetch(url, {
...options,
headers: {
...options.headers,
...headers,
},
});
}Every proof includes a timestamp. The server validates:
const MAX_DRIFT = 30000; // 30 seconds
if (Math.abs(Date.now() - proofTimestamp) > MAX_DRIFT) {
throw new Error('Proof expired');
}All signature comparisons use constant-time algorithms:
function constantTimeCompare(a: string, b: string): boolean {
let mismatch = 0;
for (let i = 0; i < a.length; i++) {
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return mismatch === 0;
}Tokens encode the deviceId. Mismatch = instant rejection.
Configurable tolerance (default: 30s) for proof timestamp validation.
import { decodeToken, verifyProof, validateTokenExpiry } from '@flux-protocol/sdk';
export function fluxAuth() {
return async (req, res, next) => {
const authHeader = req.headers.authorization;
const deviceId = req.headers['x-flux-device'];
const proof = req.headers['x-flux-proof'];
// 1. Decode token
const token = authHeader.replace('Flux ', '');
const payload = decodeToken(token);
validateTokenExpiry(token);
// 2. Lookup session (Redis)
const session = await redis.get(`session:${payload.sessionId}`);
if (!session) return res.status(401).json({ error: 'Session not found' });
// 3. Verify proof
const [timestamp, signature] = proof.split('.');
const proofPayload = `${deviceId}|${timestamp}|${token}`;
const isValid = await verifyProof(proofPayload, signature, session.proofKey);
if (!isValid) {
await redis.del(`session:${payload.sessionId}`); // Kill session
return res.status(401).json({ error: 'Invalid proof' });
}
req.session = session;
next();
};
}- ESM: ~5.2 KB (minified + gzipped)
- CJS: ~5.4 KB (minified + gzipped)
- Zero dependencies (uses Web Crypto API)
- Chrome/Edge 60+
- Firefox 55+
- Safari 11+
- Node.js 16+
import { LocalStorageAdapter } from '@flux-protocol/sdk';
FluxProtocol({
deviceId: 'custom-device-id', // Optional: auto-generated if omitted
storage: new LocalStorageAdapter({ prefix: 'myapp_' }), // Optional: default is MemoryStorageAdapter
rotationWindow: 10000, // Trigger rotation 10s before expiry
timeDriftTolerance: 60000, // Accept 60s clock drift
});npm test # Run tests
npm run test:coverage # Coverage report
npm run type-check # TypeScript validationconst token = jwt.sign({ userId: '123' }, SECRET);
// Client stores refresh token
localStorage.setItem('refresh_token', refreshToken);
// No device binding
// No instant revocation
// Manual rotationconst flux = FluxProtocol();
flux.setTokens({ accessToken, proofKey, expiresIn });
// No refresh token on client
// Device-bound cryptography
// Instant revocation
// Automatic rotationSee the examples/ directory:
basic-usage.ts- Complete authentication flowaxios-integration.ts- Axios interceptorsreact-integration.tsx- React hooks & contextserver-middleware.ts- Express middlewarestorage-adapters.ts- All storage adapter examples
Contributions welcome! Please read our Contributing Guide.
MIT © Flux Protocol Team
Built for modern authentication