Skip to content

Quick start ​

SessionTether currently exports the SessionTether class.

Set up a persistent refresh-session store and create your first session.

1. Prepare the library ​

For this repository, use Node.js 24+ and Yarn 4:

sh
git clone https://github.com/mynameisankit/session-forge.git session-tether
cd session-tether
yarn install
yarn build

The package name is session-tether. Public entry points are session-tether, session-tether/sqlite, session-tether/postgres, session-tether/utils, and session-tether/utils/password. Package-registry availability is not established by this repository; the commands above build the local source.

Migrating from SessionForge ​

Replace imports from session-forge with session-tether, including adapter and utility subpaths, and replace the SessionForge class with SessionTether. Example workspace commands now use session-tether-example-client and session-tether-example-sqlite.

The SQLite example's cookie names and JWT issuer/audience identifiers changed; sign in again after updating. PostgreSQL integration tests now use SESSION_TETHER_POSTGRES_TEST_URL.

2. Initialize SQLite ​

SQLite support uses the optional knex and sqlite3 peer dependencies, already included in this repository's development dependencies. Consumers using this adapter must provide them.

ts
import knex from 'knex';
import { SessionTether, SessionStatus } from 'session-tether';
import {
  SQLiteSessionStore,
  createSQLiteSessionTables,
} from 'session-tether/sqlite';

const db = knex({
  client: 'sqlite3',
  connection: { filename: './sessions.sqlite' },
  useNullAsDefault: true,
  pool: { min: 1, max: 1 },
});

await db.raw('PRAGMA foreign_keys = ON');
await createSQLiteSessionTables(db);

const sessions = new SessionTether({
  store: new SQLiteSessionStore(db),
  expiryTTL: 30 * 24 * 60 * 60 * 1000,
  reuseGraceTTL: 30_000,
});

expiryTTL is measured in milliseconds, not a duration string. Initialize tables before serving requests. Close the connection with await db.destroy() during application shutdown.

For PostgreSQL setup using PostgresSessionStore, see the PostgreSQL adapter guide.

3. Create a session ​

After your application authenticates a user:

ts
import { randomUUID } from 'node:crypto';

// In an application, use the authenticated user's existing UUID.
const userId = randomUUID();

const refreshToken = await sessions.createSession({
  userId,
  sessionName: 'Chrome on macOS',
});

The return value is the raw refresh-token string. Only its hash is stored. Deliver the raw token through your application's secure credential transport; do not log it.

4. Rotate the token ​

ts
const result = await sessions.rotateRefreshToken(refreshToken);

if (result.status === SessionStatus.ROTATED) {
  // Replace the client's previous refresh credential.
  const nextRefreshToken = result.refreshToken;
  // Issue your own access token for result.userId.
} else if (result.status === SessionStatus.GRACE) {
  // Your application may renew access for result.userId.
  // Do not clear or replace the refresh credential: no new one is returned.
} else {
  // End renewal and require authentication again.
}

Handle missing, revoked, consumed, and expired results as unsuccessful renewal. Validation and rotation can also throw database errors; these are not status results.

A retry of an already-rotated token within the default 30-second window returns { status: SessionStatus.GRACE, userId }. It does not issue another replacement; preserve the credential from the successful rotation. Configure reuseGraceTTL in milliseconds, or set it to 0 for immediate consumed-token rejection. Reuse at or after the boundary revokes the family.

Concurrent refresh

Both SQL adapters conditionally consume and replace a token in one transaction. Competing rotations create at most one successor, and Core applies grace/reuse policy to a losing attempt. Family revocation is rechecked under a write lock held through rotation; expiration is rechecked at the locked consumption decision point. See token lifecycle.

5. Revoke a session ​

ts
const active = await sessions.listActiveSessionsForUser(userId);

if (active[0]) {
  // Revoke by family identifier, not by raw refresh credential.
  await sessions.revokeSession(active[0].tokenHash);
}

// Or invalidate all refresh-session families for the user.
await sessions.revokeAllSessionsForUser(userId);

Already-issued stateless access tokens remain valid until their own expiration. Continue with the API reference for method signatures and return values.

Built for explicit authentication boundaries. Powered by VitePress.