Skip to main content

@ttoss/oauth-client

Reusable client for connecting a ttoss app to third-party OAuth 2.0 providers (TikTok, Instagram, YouTube, Google, …). It covers building the authorization URL, exchanging the callback code, and keeping access tokens fresh through lazy and scheduled refresh. The client is ORM- and storage-agnostic: it operates on plain token objects and delegates persistence to you, so it works with Sequelize, Prisma, or anything else.

See the OAuth Client guideline for the full end-to-end pattern (endpoints, scheduled jobs, internal token access).

Install

pnpm add @ttoss/oauth-client

Generic core

createOAuthClient builds a client for any provider. Parameter-name overrides absorb providers that deviate from RFC 6749.

import { createOAuthClient } from '@ttoss/oauth-client';

const client = createOAuthClient({
authorizationUrl: 'https://provider.example/authorize',
tokenUrl: 'https://provider.example/token',
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
});

// 1. Redirect the user to the provider
const url = client.buildAuthUrl({
redirectUri: 'https://app.example/callback',
scope: ['read', 'write'],
state: crypto.randomBytes(16).toString('hex'), // persist & verify on callback
});

// 2. Exchange the callback code for tokens
const tokens = await client.exchangeCode({
code,
redirectUri: 'https://app.example/callback',
});

Provider presets

Presets pre-configure a provider's quirks. TikTok, for example, names the client id client_key and uses comma-separated scopes — the preset encodes both.

import { createTikTokClient } from '@ttoss/oauth-client';

const tiktok = createTikTokClient({
clientKey: process.env.TIKTOK_CLIENT_KEY!,
clientSecret: process.env.TIKTOK_CLIENT_SECRET!,
});

Adding a provider is one new preset over the same core; nothing else changes.

Keeping tokens fresh

getValidToken returns a usable access token, refreshing first if it expires within refreshWindowMs (default 2h). onRefresh receives the new tokens so you can persist them.

const accessToken = await client.getValidToken(record, {
onRefresh: async (updated) => {
await saveToken(record.id, updated);
},
});

findExpiringTokens powers a scheduled job that proactively refreshes tokens expiring within a wider window (default 6h), keeping connections alive for inactive users.

import { findExpiringTokens } from '@ttoss/oauth-client';

const expiring = findExpiringTokens(await loadAllTokens());
await Promise.all(expiring.map(refreshOne));

Encryption at rest

Access and refresh tokens are credentials — encrypt them at rest. The package stays encryption-agnostic and works with plaintext records; encrypt at the storage boundary with @ttoss/auth-core:

import { decryptValue, encryptValue } from '@ttoss/auth-core';

const KEY = process.env.TOKEN_ENCRYPTION_KEY!; // from generateEncryptionKey()

const accessToken = await client.getValidToken(
{
accessToken: decryptValue({ ciphertext: row.accessToken, key: KEY }),
refreshToken: decryptValue({ ciphertext: row.refreshToken, key: KEY }),
accessTokenExpiresAt: row.accessTokenExpiresAt,
},
{
onRefresh: async (updated) => {
await row.update({
accessToken: encryptValue({ plaintext: updated.accessToken, key: KEY }),
refreshToken: encryptValue({
plaintext: updated.refreshToken,
key: KEY,
}),
accessTokenExpiresAt: new Date(Date.now() + updated.expiresIn * 1000),
});
},
}
);

API

ExportPurpose
createOAuthClientBuild a client for a generic OAuthProvider.
createTikTokClientPreset for TikTok Login Kit.
client.buildAuthUrlBuild the provider's authorization URL.
client.exchangeCodeExchange an authorization code for tokens.
client.refreshAccessTokenRefresh an access token from a stored refresh token.
client.getValidTokenReturn a valid token, refreshing lazily within a safety window.
findExpiringTokensFilter records expiring within a window (for scheduled refresh).