@tjoc/auth
v8.0.1
Published
Cognito-first JWT verification and auth primitives for the tjoc.dev portfolio
Downloads
1,231
Readme
@tjoc/auth
Cognito-first JWT verification and authentication primitives for the tjoc.dev portfolio.
v3.x is deprecated. v3 shipped real security defects and was incompatible with the portfolio's Cognito + Lambda reality. See ADR 0003 for the full rationale.
What this is
- RS256 JWT verification via JWKS — no secrets in code, ever
- AWS Cognito helpers — pre-wired verifier handling the access/id token distinction correctly
- API Gateway authorizer adapters (HTTP API v2 + REST API v1)
- Test helpers — in-memory key sets, signed JWTs, fake verifier
What this is not
- Does not own passwords, sessions, 2FA, OAuth, account lockout, or rate limiting — that is Cognito's job
- Does not open network connections or read env vars at import time — zero top-level side effects
Installation
pnpm add @tjoc/authNode ≥20. Published to npm (@tjoc/[email protected] on dist-tag pre; latest still points to v3 until the 2-week pilot in jobs.tjoc.dev completes on 2026-05-15).
# Install the pre-release pilot version explicitly:
pnpm add @tjoc/auth@preLambda authorizer (the common case)
// authorizer/index.ts
import { createCognitoVerifier } from '@tjoc/auth/cognito';
import { apiGatewayAuthorizer } from '@tjoc/auth/lambda';
const verifier = createCognitoVerifier({
region: 'eu-west-1',
userPoolId: process.env.COGNITO_USER_POOL_ID!,
clientId: process.env.COGNITO_CLIENT_ID!,
tokenUse: 'access',
});
export const handler = apiGatewayAuthorizer({ verifier });SAM template.yaml:
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
FunctionArn: !GetAtt AuthorizerFunction.Arn
AuthorizerPayloadFormatVersion: "2.0"
EnableSimpleResponses: trueThe Principal lands in downstream Lambdas at event.requestContext.authorizer.lambda:
const { sub, groups } = event.requestContext.authorizer.lambda;
const parsedGroups: string[] = JSON.parse(groups);REST API (v1) authorizer
import { restApiAuthorizer } from '@tjoc/auth/lambda';
export const handler = restApiAuthorizer({ verifier });Core verifier (framework-agnostic)
import { verifyJwt, createJwksClient } from '@tjoc/auth';
const jwks = createJwksClient({ jwksUri: 'https://.../.well-known/jwks.json' });
const principal = await verifyJwt(token, {
jwks,
issuer: 'https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_xxxxx',
audience: 'my-client-id', // omit for Cognito access tokens
algorithms: ['RS256'],
});Error handling
Every error extends AuthError and carries a typed code:
import { AuthError, TokenExpiredError } from '@tjoc/auth';
try {
await verifyJwt(token, opts);
} catch (err) {
if (err instanceof TokenExpiredError) return res.status(401).json({ error: 'token_expired' });
if (err instanceof AuthError) return res.status(401).json({ error: err.code });
throw err;
}| Class | Code | When |
|---|---|---|
| TokenExpiredError | TOKEN_EXPIRED | exp is in the past |
| TokenInvalidError | TOKEN_INVALID | Tampered, malformed, or bad signature |
| IssuerMismatchError | ISSUER_MISMATCH | iss doesn't match |
| AudienceMismatchError | AUDIENCE_MISMATCH | aud / client_id doesn't match |
| KidNotFoundError | KID_NOT_FOUND | JWKS has no key for this kid |
| AlgorithmNotAllowedError | ALGORITHM_NOT_ALLOWED | alg:none or non-allow-listed algorithm |
| ClaimsInvalidError | CLAIMS_INVALID | Missing required claim (sub, iss, exp) |
Testing in product code
import { generateTestKeyPair, signTestJwt, createFakeVerifier } from '@tjoc/auth/testing';
const kp = await generateTestKeyPair();
const verifier = createFakeVerifier({
keyPair: kp,
issuer: 'https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_test',
tokenUse: 'access',
});
const token = await signTestJwt(kp, { groups: ['admin'] });
const principal = await verifier(token);Subpath exports
| Import | Contents |
|---|---|
| @tjoc/auth | verifyJwt, createJwksClient, extractPrincipal, all error classes, all types |
| @tjoc/auth/cognito | createCognitoVerifier, CognitoVerifierConfig |
| @tjoc/auth/lambda | apiGatewayAuthorizer, restApiAuthorizer, AuthContext |
| @tjoc/auth/testing | generateTestKeyPair, signTestJwt, createInMemoryJwksClient, createFakeVerifier |
Security baseline
- No fallback secrets — missing config throws at construction, never defaults to an insecure value
- Algorithm allow-list defaults to
['RS256']—alg:noneand HMAC algorithms rejected before key lookup iss,aud,exp,nbf,kidall enforced — clock skew defaults to 5 seconds- No top-level side effects — importing this package opens no sockets and reads no env vars
- JWKS cache TTL defaults to 10 minutes with 30-second cooldown on unknown
kid
Adoption status
| Product | Status |
|---|---|
| jobs.tjoc.dev | Pilot live since 2026-05-01 (middleware/auth.ts, routes/jobs.ts) |
| star.club | After GA (2026-05-15) — createCognitoVerifier swap in post-confirmation Lambda |
| wisecv | Deferred to ADR 0007 Phase 4 |
Phase 2 (Express/Hono/Fastify adapters) ships after the pilot window closes.
