Skip to content

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.

Download the package from Downloads, check its SHA-256, and install it:

Terminal window
npm install ./tenon-auth-sdk-1.0.0.tgz
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).

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).

All errors are TenonAuthError subclasses:

  • TenonAuthUnavailableError (reason);
  • ApiKeyRejectedError, ForbiddenError;
  • InvalidRequestError, NotFoundError, InvalidCodeError;
  • FactorLockedError, TooManyRequestsError;
  • EmailRefusedError, EmailUnavailableError;
  • ConflictError.

See Errors.