TypeScript and JavaScript
Package: @tenon-auth/sdk 1.0.0.
- Runtimes: Node.js 20+, Deno, Bun or Cloudflare Workers. It uses
fetch, has no dependencies, and ships ESM and CommonJS with type declarations. - Server only: it refuses to run in a browser, where the API key would be public.
Install
Section titled “Install”Download the package from Downloads, check its SHA-256, and install it:
npm install ./tenon-auth-sdk-1.0.0.tgzConfigure
Section titled “Configure”import { TenonAuthClient } from '@tenon-auth/sdk';
export const tenonAuth = new TenonAuthClient({ baseUrl: process.env.TENON_AUTH_URL!, // https://api.glow-kin.com apiKey: process.env.TENON_AUTH_API_KEY!, // tnk_...});Options:
timeoutMs(30 000);headers;fetch, your own (a proxy agent).
The key is kept in a private field, and console.log, util.inspect and JSON.stringify show (hidden).
Sign in (Express)
Section titled “Sign in (Express)”import { TenonAuthUnavailableError, type SecondStep } from '@tenon-auth/sdk';
app.post('/login', async (req, res) => { const account = await checkPassword(req.body.username, req.body.password); // yours let step: SecondStep; try { step = await tenonAuth.beginSecondStep(account.id, { context: { ipAddress: req.ip, userAgent: req.get('user-agent') }, // app.set('trust proxy', ...) for req.ip trustedDeviceToken: req.cookies.tenon_device, }); } catch (error) { if (error instanceof TenonAuthUnavailableError) return emergencyOrRefuse(req, res, account); // fail closed throw error; } req.session.secondStep = step; // plain data: fine in any session store if (step.kind === 'CODE_ENTRY') return res.redirect('/login/code'); if (step.kind === 'PUSH_WAIT') return res.redirect('/login/approve'); if (step.kind === 'RECOVERY_ONLY') return res.redirect('/login/recover'); return signIn(req, res, account); // TRUSTED or NO_METHOD});
app.post('/login/code', async (req, res) => { const result = await tenonAuth.completeWithCode(req.session.secondStep, req.body.code); if (result.approved) return signIn(req, res); if (result.status === 'WRONG_CODE') return res.redirect(`/login/code?left=${result.attemptsRemaining}`); return res.redirect('/login'); // FAILED, EXPIRED});The waiting page polls tenonAuth.pollApproval(step) every 2 seconds. The recovery page calls tenonAuth.completeWithRecoveryCode(step, code).
Errors
Section titled “Errors”All errors are TenonAuthError subclasses:
TenonAuthUnavailableError(reason);ApiKeyRejectedError,ForbiddenError;InvalidRequestError,NotFoundError,InvalidCodeError;FactorLockedError,TooManyRequestsError;EmailRefusedError,EmailUnavailableError;ConflictError.
See Errors.