Service identity and authorization for modern JavaScript runtimes.
Warning: Trustline is not production-ready yet. Do not use it in production environments. Use it only in side projects.
Trustline is a machine-to-machine authentication library for internal services. It is designed around dedicated core and integration entry points:
trustline: provider, guard, memory storage, and shared core exportstrustline/client: token fetching and caching for outgoing requeststrustline/frameworks/*: framework adapters for receiving servicestrustline/adapters/*: SQL storage adapters
The package now ships the first full stack: provider, client, guard, framework adapters, memory storage, and SQL storage adapters for SQLite, Postgres, and MySQL.
Trustline is intended to be consumed as the trustline package:
bun add trustlineOr with npm:
npm install trustlineInstall only the integrations you use. For example, Express users install express; SQLite users install better-sqlite3 and kysely.
Example installs:
npm install trustline express
npm install trustline better-sqlite3 kyselyIf you are working from this repository before package publication, build the package locally and install or link it from the repo source.
Provider:
import { createProvider, memoryStorage } from "trustline";
const provider = createProvider({
issuer: "https://auth.internal",
storage: memoryStorage(),
env: "production",
});
const service = await provider.clients.create({
name: "order-processor",
scopes: ["read:orders", "write:inventory"],
});Provisioning from SQLite-backed provider state can also be done with the standalone admin binary:
trustline-cli client create \
--issuer https://auth.internal \
--sqlite-path ./trustline.sqlite \
--name order-processor \
--scope read:orders \
--scope write:inventoryClient:
import { createClient } from "trustline/client";
const client = createClient({
tokenUrl: "https://auth.internal/token",
clientId: service.clientId,
clientSecret: service.clientSecret,
audience: "inventory-service",
});
const token = await client.getToken();Guard:
import { createGuard } from "trustline";
const guard = createGuard({
issuer: "https://auth.internal",
audience: "inventory-service",
scopes: ["read:orders"],
env: "production",
});
const identity = await guard.verify(token);Trustline derives the JWKS endpoint automatically for verification:
issuer: https://auth.internal
jwks: https://auth.internal/.well-known/jwks.jsonOperational controls already implemented in the current provider include requested-scope narrowing, token revocation by jti, client disable and re-enable, client token cutoffs, and signing key rotation with overlap windows.
Trustline does not need a Bun-specific adapter. Bun already uses the standard Web Request and Response APIs, so use the provider's handle() method directly and call guard.verify() inside your fetch handler.
import { createGuard, createProvider, memoryStorage } from "trustline";
const provider = createProvider({
issuer: "https://auth.internal",
storage: memoryStorage(),
});
Bun.serve({
port: 3000,
fetch: provider.handle,
});
const guard = createGuard({
issuer: "https://auth.internal",
audience: "inventory-service",
});
Bun.serve({
port: 4000,
fetch: async (request) => {
const header = request.headers.get("authorization");
const token = header?.replace(/^Bearer\s+/, "") ?? "";
const identity = await guard.verify(token);
return Response.json({
caller: identity.name ?? identity.clientId,
});
},
});import express from "express";
import { createGuard, createProvider, memoryStorage } from "trustline";
import {
createExpressGuard,
createExpressProvider,
type TrustlineRequest,
} from "trustline/frameworks/express";
const app = express();
const provider = createProvider({
issuer: "https://auth.internal",
storage: memoryStorage(),
});
const guard = createGuard({
issuer: "https://auth.internal",
audience: "inventory-service",
});
app.use(createExpressProvider(provider));
app.use(createExpressGuard(guard));
app.get("/internal", (request: TrustlineRequest, response) => {
response.json({
caller: request.trustline?.name ?? request.trustline?.clientId,
});
});Current public API includes:
interface ProviderOptions {
issuer: string;
storage: StorageAdapter;
signing?: {
algorithm?: "ES256" | "RS256";
privateKey?: string;
keyId?: string;
};
token?: {
ttl?: number;
};
hooks?: {
onEvent?(event: ProviderEvent): void | Promise<void>;
};
env?: string;
}
interface GuardOptions {
issuer: string;
jwksUrl?: string;
audience?: string | string[];
scopes?: string[];
env?: string;
clockTolerance?: number;
hooks?: {
onEvent?(event: GuardEvent): void | Promise<void>;
};
}
interface ServiceIdentity {
clientId: string;
name: string | null;
scopes: string[];
env: string | null;
raw: Record<string, unknown>;
}
interface ProviderClient {
clientId: string;
name: string;
scopes: string[];
active: boolean;
lastSeenAt: Date | null;
secretLastRotatedAt: Date | null;
nextSecretExpiresAt: Date | null;
hasPendingSecretRotation: boolean;
}Adapter surface:
provider.handle(request)provider.clients.create/list/get/rename/updateScopes/rotateSecret/revoke/disable/enable/invalidateTokensBefore/clearTokensInvalidBeforeprovider.keys.rotateprovider.tokens.revokeguard.verify(token)createExpressProvider(provider)createExpressGuard(guard)createFastifyProvider(provider)createFastifyGuard(guard)createHonoProvider(provider)createHonoGuard(guard)
Supported signing algorithms:
RS256ES256
Bundled storage adapters via dedicated subpaths:
memoryStorage()sqliteStorage(path | database)postgresStorage(pool)mysqlStorage(pool)
SQL adapters follow the Better Auth-style pattern of receiving ready-made database handles:
import Database from "better-sqlite3";
import { createPool as createMysqlPool } from "mysql2";
import { Pool as PostgresPool } from "pg";
import { mysqlStorage } from "trustline/adapters/mysql";
import { postgresStorage } from "trustline/adapters/postgres";
import { sqliteStorage } from "trustline/adapters/sqlite";
const sqlite = sqliteStorage(new Database("./trustline.sqlite"));
const postgres = postgresStorage(
new PostgresPool({ connectionString: process.env.DATABASE_URL })
);
const mysql = mysqlStorage(createMysqlPool(process.env.DATABASE_URL!));trustline/client now accepts an optional async token cache interface for sharing access tokens across instances while keeping the default in-memory cache behavior.
Client secrets are only returned by provider.clients.create() and provider.clients.rotateSecret(). provider.clients.get() and provider.clients.list() return ProviderClient metadata and never expose hashed secrets.
Build the package:
bun run buildRun type checks:
bun run typecheckRun tests:
bun run testRun formatting and lint checks:
bun run checkMIT