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.
Installation
Trustline is intended to be consumed as the trustline package:
Or with npm:
Install 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 kysely
If you are working from this repository before package publication, build the package locally and install or link it from the repo source.
Quick start
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:inventory
Client:
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.json
Operational 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.
Bun
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, }); }, });
Express
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, }); });
API
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.
Development
Build the package:
Run type checks:
Run tests:
Run formatting and lint checks:
License
MIT