Skip to content

FluxHookHQ/flux-protocol

Repository files navigation

FluxProtocol SDK

FluxProtocol SDK — Device-Bound Authentication

npm version Bundle size License: MIT

Why FluxProtocol?

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)

Installation

npm install @flux-protocol/sdk
yarn add @flux-protocol/sdk
pnpm add @flux-protocol/sdk

Quick Start

import { 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();

Storage Options

FluxProtocol SDK supports multiple storage adapters for token persistence:

1. Memory Storage (Default)

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


2. LocalStorage Adapter (Browser)

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


3. Secure Cookies Adapter (Browser/SSR)

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


4. IndexedDB Adapter (Browser)

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


5. Custom Storage Adapter

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)

Storage Benefits

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

Core Concepts

1. Device Binding

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 deviceId

2. Cryptographic Proof

Every 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.signature

The proof signature is: HMAC-SHA256(deviceId|timestamp|accessToken, proofKey)

3. Automatic Rotation

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
});

4. Server-Controlled Sessions

All session state lives on the server (Redis recommended). Instant revocation:

// Logout device
DELETE /sessions/{deviceId}

// Logout all devices
DELETE /sessions/user/{userId}

API Reference

setTokens(tokens)

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
});

buildHeadersAsync()

Generate HTTP headers for requests (async).

const headers = await flux.buildHeadersAsync();
// {
//   Authorization: 'Flux AT....',
//   'X-Flux-Device': 'deviceId',
//   'X-Flux-Proof': 'timestamp.signature'
// }

updateAccess(accessToken, expiresIn)

Update access token after rotation.

flux.updateAccess(newAccessToken, newExpiresIn);

onRotate(callback)

Register a callback for rotation events.

flux.onRotate(async (event: RotateEvent) => {
  // event.timestamp, event.currentToken, event.expiresAt
  await refreshToken();
});

onError(callback)

Register an error handler.

flux.onError((event: ErrorEvent) => {
  console.error(`[${event.code}]: ${event.message}`);
});

stop()

Stop the client and clear all timers/tokens.

flux.stop();

Utility Methods

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 signature

Integration Examples

Axios

import 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);
  }
);

React

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);

Fetch Wrapper

async function authenticatedFetch(url: string, options: RequestInit = {}) {
  const headers = await flux.buildHeadersAsync();

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      ...headers,
    },
  });
}

Security Features

Replay Attack Prevention

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');
}

Timing Attack Mitigation

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;
}

Token Binding

Tokens encode the deviceId. Mismatch = instant rejection.

Clock Drift Handling

Configurable tolerance (default: 30s) for proof timestamp validation.

Server Implementation

Express Middleware

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();
  };
}

Bundle Size

  • ESM: ~5.2 KB (minified + gzipped)
  • CJS: ~5.4 KB (minified + gzipped)
  • Zero dependencies (uses Web Crypto API)

Browser Support

  • Chrome/Edge 60+
  • Firefox 55+
  • Safari 11+
  • Node.js 16+

Configuration

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
});

Testing

npm test                 # Run tests
npm run test:coverage    # Coverage report
npm run type-check       # TypeScript validation

Migration from JWT

Before (JWT)

const token = jwt.sign({ userId: '123' }, SECRET);

// Client stores refresh token
localStorage.setItem('refresh_token', refreshToken);

// No device binding
// No instant revocation
// Manual rotation

After (FluxProtocol)

const flux = FluxProtocol();
flux.setTokens({ accessToken, proofKey, expiresIn });

// No refresh token on client
// Device-bound cryptography
// Instant revocation
// Automatic rotation

Examples

See the examples/ directory:

Contributing

Contributions welcome! Please read our Contributing Guide.

License

MIT © Flux Protocol Team

Links


Built for modern authentication

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published