@tapokjs/sdk
v0.1.0
Published
Browser and server SDK for Tapok authentication
Maintainers
Readme
@tapokjs/sdk
Готовый TypeScript SDK для Tapok. Пакет построен на @tapokjs/core и публикует два ESM entry point:
@tapokjs/sdk/browserдля Base в браузере и навигации Connect;@tapokjs/sdk/serverдля Connect на сервере и проверки Base identity token.
Полное руководство: tapok.orria.space/docs/sdk.
Установка
bun add @tapokjs/sdkНе используйте root entry point в прикладном коде. Импортируйте только /browser или /server.
Base в браузере
Сначала разместите Base manifest на production origin. Инструкция и точный формат: Base manifest.
import { createBaseBrowserClient } from "@tapokjs/sdk/browser";
const tapok = createBaseBrowserClient({
redirectUri: "https://app.example/auth/tapok/callback",
scopes: ["session", "age:18"],
});
// Страница входа.
await tapok.redirect();
// Страница callback.
const result = await tapok.completeCallback();
if (result.ok) {
await fetch("/api/session/tapok", {
method: "POST",
credentials: "same-origin",
headers: {
"content-type": "application/json",
"x-csrf-token": csrfToken,
},
body: JSON.stringify({ identityToken: result.response.identity_token }),
});
}Клиент хранит Base transaction в sessionStorage, проверяет state, exact iss и nonce, затем удаляет query callback до exchange. Отправляйте identity token своему серверу same-origin POST с CSRF-защитой. Не делайте сессию прямо в браузере.
На сервере проверьте JWT и атомарно погасите jti:
import { TAPOK_ISSUER } from "@tapokjs/core";
import { verifyAndConsumeBaseIdentityToken } from "@tapokjs/sdk/server";
const verified = await verifyAndConsumeBaseIdentityToken({
token: body.identityToken,
expectedIssuer: TAPOK_ISSUER,
expectedAudience: "origin:https://app.example",
replayStore: {
async consume(jti, expiresAt) {
// Вставьте jti с unique constraint и TTL до expiresAt.
return await reserveJtiOnce(jti, expiresAt);
},
},
});
await createApplicationSession(verified.claims.sub);verifyAndConsumeBaseIdentityToken() допускает только ES256 и проверяет kid, подпись, issuer, audience, сроки, mode и pairwise_sub. Token живёт 10 минут. Повторный jti должен завершать вход ошибкой.
Для popup доступны openPopup(), completePopupMessage() и postBasePopupCallback(). Проверяйте event.origin и event.source. targetOrigin всегда равен exact origin приложения, не *. Redirect в текущей вкладке остаётся обязательным fallback.
Connect на сервере
Connect требует backend. Не передавайте в браузер client secret, PKCE verifier, authorization code, ID token или access token. Перед началом зарегистрируйте приложение, callback URI, цели scopes и получите secret в Developer portal.
import { createConnectServerClient } from "@tapokjs/sdk/server";
const tapok = createConnectServerClient({
clientId: process.env.TAPOK_CLIENT_ID!,
clientSecret: process.env.TAPOK_CLIENT_SECRET!,
redirectUri: "https://app.example/auth/tapok/callback",
scopes: ["openid", "email"],
transactions: transactionStore,
});
// GET /auth/tapok/start
const { authorizationUrl } = await tapok.beginAuthorization();
return Response.redirect(authorizationUrl, 302);
// GET /auth/tapok/callback
const completed = await tapok.completeCallback(request.url);
if (!completed.ok) {
return Response.redirect("https://app.example/login?error=access_denied", 303);
}
await createApplicationSession(completed.userInfo.sub);
// completed.userInfo.email — технический, недоставляемый адрес, не используйте для связи.
return Response.redirect("https://app.example/account", 303);transactions должен реализовать контракт:
import type { ConnectTransactionStore } from "@tapokjs/sdk/server";
const transactionStore: ConnectTransactionStore = {
async save(transaction) {
// Сохраните в server-side encrypted storage с TTL не больше transaction.expiresAt.
},
async consume(state) {
// Одной атомарной операцией верните и удалите непросроченную transaction.
return null;
},
};SDK формирует client_secret_basic, обменивает code, проверяет ES256 ID token через Tapok JWKS, сверяет nonce, загружает UserInfo и сравнивает его sub с sub ID token. Access token остаётся на сервере. После callback перенаправьте пользователя на allowlisted локальный URL без OAuth query.
Отзыв access token:
await tapok.revoke(accessToken);createConnectBrowserNavigator() подходит только для открытия вашего /auth/tapok/start из браузера. Он не реализует Connect protocol и не получает секреты или tokens.
Полезные ссылки
Development
cd @tapokjs
bun run --cwd sdk check