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:
git clone https://github.com/mynameisankit/session-forge.git session-tether
cd session-tether
yarn install
yarn buildThe 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.
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:
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
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
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.
