ts-cognito
v0.5.0
Published
Shared Amazon Cognito authentication helpers for TypeScript applications.
Maintainers
Readme
ts-cognito
Shared Amazon Cognito authentication helpers for TypeScript applications that use a consistent user-pool configuration. The package also retains its existing user and admin SDK wrappers.
Installation and runtime
npm install ts-cognito
# For TypeScript Node.js applications that do not already have Node.js types:
npm install --save-dev @types/nodeRequires Node.js 22 or newer. It publishes CommonJS JavaScript and TypeScript declarations; both require('ts-cognito') and named ESM imports are supported. Published declarations are checked with TypeScript 5.6 and 6.0. Browser bundles and other runtimes are not part of the current validation matrix. Admin operations require AWS credentials with the appropriate IAM permissions.
Refresh tokens
Use an app client without a client secret, as used by the existing shared configuration. Set the region explicitly or use the AWS SDK's normal region configuration:
import { CognitoClient } from 'ts-cognito';
const cognito = new CognitoClient('your-app-client-id', { region: 'us-east-1' });
const tokens = await cognito.refreshTokens(storedRefreshToken);
// Save both values in your application's session, including a rotated token.
session.set('token', tokens.accessToken);
session.set('refresh', tokens.refreshToken);refreshTokens(refreshToken, options?) returns Promise<RefreshedTokens>:
| Field | Meaning |
| --- | --- |
| accessToken: string | Required, non-empty access token. |
| refreshToken: string | Replacement token when returned by Cognito; otherwise the input refresh token. |
| expiresIn: number | Positive integer access-token lifetime in seconds reported by Cognito, not an absolute timestamp. |
| idToken?: string | ID token when returned by Cognito. |
The helper uses GetTokensFromRefreshToken, which supports both rotating and non-rotating refresh tokens. It does not enable rotation or change AWS resources. The original token is preserved only when RefreshToken is absent; an empty replacement is treated as an invalid response.
For pools with device remembering enabled, pass the confirmed device key captured during authentication:
const tokens = await cognito.refreshTokens(storedRefreshToken, {
deviceKey: storedConfirmedDeviceKey,
});The helper does not register or confirm devices. App clients with secrets and custom ClientMetadata workflows are outside this helper's current scope; use the AWS SDK directly for those configurations.
Errors and responsibilities
- Empty refresh tokens and invalid supplied device keys throw
TypeErrorbefore making an AWS request. - A successful response missing a usable access token or positive integer
ExpiresIn, or containing an invalid optional token, throwsInvalidTokenRefreshResponseError. Its message and properties do not contain tokens or the AWS response. - AWS SDK and transport errors propagate unchanged. An expired credential, throttling, and an outage are not collapsed into the same
nullresult. Callers decide whether to request sign-in, return a temporary failure, or retry according to their application policy. SDK retry behavior comes from the SDK configuration; the helper adds no retries or fallback API calls.
The package does not read cookies, persist sessions, decide when to refresh, verify JWT signatures, or choose HTTP responses. It performs no logging in refreshTokens(). The existing jwtDecode() and isExpiredToken() utilities do not verify signatures and must not be treated as authentication checks.
When rotation is enabled, persist the returned refresh token together with the access token. Coordinate concurrent refreshes in the application's session/storage layer; the helper does not serialize requests across processes. See AWS refresh-token behavior.
Migrating consumers after the npm release
Existing refreshTokenAuth() and getAuthenticationTokens() calls continue to work as before. refreshTokenAuth() uses REFRESH_TOKEN_AUTH, which is incompatible with refresh-token rotation. Only the new method uses GetTokensFromRefreshToken.
After 0.4.0 has been published to npm, update original-track-web and octerv-auth to the published release (including their lockfiles). Do not point their production dependencies at this checkout or a local tarball.
Both applications currently repeat the following logic:
const response = await cognito.refreshTokenAuth(refreshToken);
const auth = cognito.getAuthenticationTokens(response);
if (auth.exception === '') {
token = auth.accessToken;
refresh = refreshToken;
}Replace that block inside the existing application try/catch with:
const tokens = await cognito.refreshTokens(refreshToken);
token = tokens.accessToken;
refresh = tokens.refreshToken;Keep session access, session persistence, route authorization, user lookups, and error-to-HTTP handling in each application. Treat InvalidTokenRefreshResponseError as a provider failure rather than a rejected user credential. Verify that GetTokensFromRefreshToken is available for the deployed region/pool configuration before rollout; this preparation does not test a live pool.
For original-track-web, start with app/utils/cognito.server.ts. For octerv-auth, start with app/utils/cognito.server.ts, then migrate the refresh blocks in app/routes/_auth.delete.tsx and app/routes/_auth.password.update.tsx. Add application tests for successful refresh, credential rejection, temporary provider failure, and saving replacement refresh tokens. These consumer edits are intentionally scheduled after publication.
Passkey sign-in (0.5.0)
CognitoPasskeyClient handles Cognito's USER_AUTH / WEB_AUTHN exchange and validates its results. It requires an app client without a secret, a pool with passkeys and required user verification enabled, and an app client with ALLOW_USER_AUTH. See the AWS authentication flow documentation and WebAuthn configuration.
import { CognitoPasskeyClient, PasskeyAuthenticationError } from 'ts-cognito';
// Trusted server configuration: never derive these values from request input.
const passkeys = new CognitoPasskeyClient({
clientId: 'your-app-client-id',
rpId: 'auth.example.com',
origin: 'https://auth.example.com',
}, { region: 'us-east-1' });
const { context, options } = await passkeys.startAuthentication(email);
// Store context server-side, bound to this browser and app client, with a TTL
// and atomic one-time consumption. Return ONLY options and an opaque attempt ID
// to the browser. Do not expose context.username or context.session.On the completion request, first consume the attempt in the application. Pass the stored context and the assertion JSON from the browser:
const tokens = await passkeys.completeAuthentication(storedContext, credentialJSON);
// Persist tokens.accessToken and tokens.refreshToken in the application's session.
// tokens.expiresIn is a lifetime in seconds; tokens.idToken is optional.Public API:
| Method | Result |
| --- | --- |
| startAuthentication(username) | { options: PasskeyRequestOptions, context: PasskeyAuthenticationContext } |
| completeAuthentication(context, credentialJSON: unknown) | AuthenticationTokens (accessToken, refreshToken, expiresIn, optional idToken) |
options contains JSON WebAuthn request options with base64url strings, not browser ArrayBuffer values. Additional Cognito option fields are preserved. context contains the canonical Cognito username, private Cognito session, and normalized challenge. Completion uses that username and session rather than accepting replacements from the browser.
The library requires the expected RP ID and userVerification: "required", validates request-option shape, and rejects unexpected password, OTP, or MFA challenges instead of selecting a different method. It validates the assertion JSON, ceremony type, saved challenge, exact origin, and absence of cross-origin authentication before sending it to Cognito. Cognito performs the cryptographic authenticator/signature verification. Only a final response with non-empty access/refresh tokens, a positive integer expiry, and no further challenge is accepted. The original assertion JSON is not changed. Serialized credentials are limited to 96 KiB.
PasskeyAuthenticationError has a sanitized message and one of these stable codes:
| Code | Meaning |
| --- | --- |
| unexpected_challenge | Start did not return the expected pending WebAuthn challenge. |
| invalid_options | Malformed options or RP/user-verification policy mismatch. |
| invalid_credential | Malformed or unbound browser assertion; Cognito is not called. |
| authentication_incomplete | Completion returned another challenge or incomplete tokens. |
Invalid trusted configuration/context and empty usernames throw TypeError. SDK/transport errors propagate unchanged, allowing each application to distinguish throttling, rejected credentials, and provider failure. The library does not log credentials, responses, or session context. Decide safe UI messages and HTTP status codes in the application.
Application responsibilities and scope
Keep browser navigator.credentials calls and binary/JSON conversion, CSRF/Origin checks on HTTP requests, reCAPTCHA/rate limiting, redirects, cookies, session persistence, authentication-attempt storage, cancellation, TTL validation, and atomic consumption in the application. Call completeAuthentication() only with context recovered from a browser- and app-client-bound attempt. The library is stateless and cannot prevent replay or expire stored attempts by itself.
This API covers sign-in. Passkey registration, credential listing/deletion, password reauthentication before management, and the browser UI remain outside this release. Existing respondToAuthChallenge() remains specific to NEW_PASSWORD_REQUIRED.
Reuse in Original Track
The implementation contains no Octerv hostnames, cookie names, or storage assumptions. For an independently configured Original Track pool/client, the same API can use, for example, rpId: 'app.original-track.com' and origin: 'https://app.original-track.com'. Confirm the actual RP configuration before enabling it. Sharing this library does not make a credential registered for auth.octerv.com valid at another RP/domain. Choose the appropriate pool/RP settings or an explicit central authentication-domain design. The library currently supports one exact HTTPS origin (or HTTP localhost for development) compatible with the configured RP ID, not related-origin or native-app association flows.
Migrating octerv-auth
After 0.5.0 is published, update its dependency and lockfile from npm. Replace the Cognito exchange and assertion/result validation in app/utils/passkey-signin.server.ts with startAuthentication() and completeAuthentication(). Save started.context in the existing attempt store, send only started.options to the browser, and retain all browser binding, TTL, one-time consumption, admin-intent checks, cookies, and safe error-to-HTTP mapping. No browser/API route contract needs to change. Existing registration and management code continues using its current SDK APIs.
Existing user and admin clients
import { CognitoClient, CognitoAdminClient } from 'ts-cognito';
const client = new CognitoClient('your-app-client-id', { region: 'us-east-1' });
const response = await client.initiateAuth('[email protected]', 'password');
// Uses the AWS SDK's normal region and credential resolution.
const admin = new CognitoAdminClient('your-user-pool-id');
const user = await admin.getUser('username');The existing user methods are signUp, confirmSignUp, resendConfirmationCode, initiateAuth, refreshTokenAuth, getAuthenticationTokens, globalSignOut, getUser, updateUserAttributes, respondToAuthChallenge, changePassword, forgotPassword, confirmForgotPassword, and deleteUser. They retain their existing SDK-oriented inputs and outputs. respondToAuthChallenge handles NEW_PASSWORD_REQUIRED only. initiateAuthSRP remains unimplemented.
Admin methods are isExistUser, listUsers, filterUsers, getUser, disableUser, enableUser, setAttribute, and deleteUser. The legacy isExistUser catches all SDK errors and returns false; use getUser with explicit exception handling when absence must be distinguished from an outage.
See the generated TypeScript declarations for complete signatures and the AWS SDK reference for SDK types.
Development and release
npm ci
npm run check
npm run test:packageThe development-only override of @istanbuljs/load-nyc-config to js-yaml 4 removes its vulnerable argparse/sprintf-js chain while retaining the YAML load() API used by Istanbul. It does not affect installed consumers.
Development uses Node.js 24.22.1 (the security-patched LTS) as pinned in .node-version. See the Node.js release notes. CI covers Node.js 22 and 24. TypeScript is pinned to the compatible 6.0.x line until ts-jest supports version 7.
check builds from a clean output directory, type-checks the tests, and runs mocked unit tests. test:package packs and installs a temporary tarball and checks its contents, CommonJS loading, named ESM imports, and TypeScript 5.6/6.0 consumers. It requires access to npm for the isolated consumer's dependencies and does not publish anything. npm pack also builds from source through prepare.
Before release:
- Review the version and changelog, and change the unreleased heading to the actual release date.
- Run
npm ci,npm run check,npm run test:package, andnpm audit. - Inspect
npm pack --dry-runto confirm only compiled output, package metadata, README, changelog, and license are included. - Report the reviewed changes, verification results, and intended version before publishing.
- Commit the release sources and documentation, then create the matching annotated Git tag (for example,
v0.5.0). - Publish with
npm publishusing the maintainer's npm credentials/2FA.prepublishOnlyrepeats the checks and packed-package verification. - Confirm the published version, then upgrade and test the consuming applications.
The legacy npm run test-aws command targets a real user pool and includes account-changing operations. It is not run by the normal checks or CI; use it only against a dedicated disposable test pool.
