@anarchitects/auth-ts
Public HTTP authority belongs to anarchitecture-contracts
under ADR-0016.
This implementation pins @anarchitects/auth-contracts@0.1.0 and derives
its transitional TypeBox DTO exports from that published canonical graph. Domain
models remain local. Nest keeps its Fastify validation and serialization adapter;
Angular keeps the existing DTO imports. See the adoption evidence
and TS Contracts Guide for scope and limits.
TypeScript consumers must use package-export-aware resolution (NodeNext,
Node16 where supported by their module graph, or bundler). Legacy node10
resolution cannot resolve the canonical packages. Runtime ESM and CommonJS entry
points remain available.
TypeScript DTOs and domain models for the Anarchitecture authentication stack. The package bundles:
- TypeBox compatibility bindings derived from canonical Zod request/response schemas
- Type-safe DTO aliases consumed by Angular and Nest libraries and reflected in generated OpenAPI docs
- Domain models (
AuthUser,Role,Permission) for composing dynamic RBAC logic across services - Contract-driven auth profile factories that generate backend request schemas and frontend form metadata from one config
Use it to validate inbound/outbound payloads, share typings between Angular/Nest bricks, and keep auth-specific logic consistent across tiers.
The unreleased 0.12 line uses explicit .js relative specifiers in emitted
declarations for strict NodeNext consumers. DTOs, models, public entry points and
CJS/ESM runtime formats are preserved. This transitive declaration fix is validated
by the Auth Nest 11/12 packed-consumer matrix
and can be backported independently to the 0.11.x maintenance line.
Migration guidance for the Better Auth realignment lives in the auth migration guide. Migration guidance for the contract-driven auth profile model lives in the auth contract migration guide.
Developer + AI Agent Start Here
- Read this README before generating DTO/model code that depends on
@anarchitects/auth-ts. - Keep auth domain models and policy semantics here; public HTTP DTO authority belongs to Contracts, with existing exports retained during transition.
- Prefer public exports (
@anarchitects/auth-ts,/dtos,/models) and avoid internal path imports. - Keep framework-specific behavior in Angular/Nest packages, not in this TS contract package.
Features
- Canonical Auth schemas exposed through TypeBox bindings shared by Angular and Nest
- Domain model contracts for users, roles, and permissions
- Reusable validation + type-inference building blocks for auth flows
- Contract profile config, compatibility checks, and schema factories for runtime/frontend alignment
Authorization Contract
@anarchitects/auth-ts owns transport-neutral policy semantics and evaluation conformance, but does not enforce frontend or backend authorization by itself. Contracts owns the HTTP representation; mappings must preserve these semantics without loss.
PolicyRule
Serialized RBAC rules use the following contract:
- required:
action,subject - optional:
conditions,fields,inverted,reason actionandsubjectstay open strings for compatibility across apps- malformed rule payloads are rejected fail-closed by the shared DTO parsers
This is the contract emitted by /auth/me, persisted through auth permission mapping, and consumed by Angular/Nest authorization helpers.
RoutePolicy
RoutePolicy is intentionally narrower than PolicyRule:
- shape:
{ action, subject } - purpose: coarse route-attempt checks
- not a substitute for instance-level authorization
Use RoutePolicy when a consumer only needs to answer "may this user attempt this kind of work at all?" Concrete ownership or field-sensitive checks still belong to loaded resources and CASL ability evaluation in Angular/Nest layers.
This is the shared contract behind @anarchitects/auth-declarations route
metadata and the coarse route-attempt helpers in @anarchitects/auth-angular.
It is intentionally not the full resource-authorization contract.
Installation
npm install @anarchitects/auth-ts
# or
yarn add @anarchitects/auth-ts
# or
pnpm add @anarchitects/auth-ts
Entry points
| Import path | Description |
|---|---|
@anarchitects/auth-ts |
Barrel re-export for core models plus the core/session DTO surface |
@anarchitects/auth-ts/contracts |
Contract profile config, schema factories, compatibility helpers, and payload shaping |
@anarchitects/auth-ts/dtos |
Core/session request-response schemas and DTO types (TypeBox) |
@anarchitects/auth-ts/dtos/jwt |
JWT plugin-specific DTO types and schemas |
@anarchitects/auth-ts/dtos/passkeys |
Passkey registration/authentication DTOs and route schemas |
@anarchitects/auth-ts/models |
Domain models used for user/session/RBAC composition |
Passkey ceremony contracts
Import the optional passkey contracts from @anarchitects/auth-ts/dtos/passkeys.
They are not re-exported by the root or core DTO entry point. This package supplies
shared schemas and inferred TypeScript types. Nest server verification consumes
these contracts through AuthPasskeyService; package-owned HTTP endpoints
and Angular browser orchestration remain work
under epic #105.
| Operation | Request | Successful response |
|---|---|---|
| Registration begin | { authenticatorAttachment?: 'platform' | 'cross-platform' } |
WebAuthn creation options in JSON form |
| Registration finish | { response: PasskeyRegistrationCredentialDTO, name?: string } |
Existing { success: boolean } envelope |
| Authentication begin | {} for discoverable credentials |
WebAuthn request options in JSON form |
| Authentication finish | { response: PasskeyAuthenticationCredentialDTO } |
Existing { user, rbac } session envelope |
Each operation exports a request schema, response schema, their DTO types, and a
route schema. For example:
import { PasskeyRegistrationFinishRequestSchema, PasskeyRegistrationFinishRouteSchema, type PasskeyRegistrationFinishRequestDTO } from '@anarchitects/auth-ts/dtos/passkeys';
import { Value } from '@sinclair/typebox/value';
function validateRegistration(payload: unknown): payload is PasskeyRegistrationFinishRequestDTO {
return Value.Check(PasskeyRegistrationFinishRequestSchema, payload);
}
// Nest presentation can pass this directly to @RouteSchema once routes exist.
PasskeyRegistrationFinishRouteSchema;
The route schemas contain only body and response fields. HTTP paths and methods
belong to Nest presentation; OpenAPI operation IDs and tags belong to the central
spec tooling. Angular and Nest should import these types and schemas rather than
copying them or exposing an authentication engine's internal types.
Registration requires an authenticated user. The server derives user identity, relying-party policy, and the expected challenge. Begin/finish correlation uses cookies, so adapters must preserve them across both requests. Authentication finish establishes the normal session and returns the shared user/RBAC envelope. Client-controlled user IDs, expected challenges, and verification policy are not accepted in request envelopes. A registration label is optional; supplied labels must contain a non-whitespace character and be at most 255 characters.
Binary fields use non-empty, unpadded base64url strings. Browser adapters must
convert JSON options to browser inputs and serialize credentials before sending
them. The shapes follow the WebAuthn JSON representations
and accommodate SimpleWebAuthn browser serializers.
Registration convenience fields are optional for older serializers; an assertion's
userHandle may be absent or null. Adapters should normalize null when their verifier
expects an omitted value. Transport hints accept future string values, and extension
objects remain opaque to allow extension-specific data.
Schema validation checks transport structure. The server must still verify the
challenge, origin, RP ID, credential ownership, signature, user verification, and
counter policy, including consistency between id and rawId. These schemas do
not perform cryptographic verification or enforce decoded binary lengths.
Existing passkey DTO exports follow this package's versioning and CI release workflow. Canonical HTTP contract versions are independent and require coordinated adoption under ADR-0016. There is no separate runtime contract-version constant or ceremony-body version. Tightening constraints or changing required fields is a breaking contract change.
Contract Profiles
@anarchitects/auth-ts owns the existing local auth-profile configuration/factory seam used by Nest and Angular. It remains part of the transitional baseline; profile customization must be audited against the applicable canonical HTTP contract before claiming conformance.
AuthContractConfigdefines the profile shape for register, login, forgot-password, reset-password, verify-email, change-password, and logout.DefaultAuthContractConfigis the published default profile.createAuthContracts(config)derives request schemas and form metadata from one profile.assertContractCompatibility(config, expectedVersion)fails fast when a consumer expects a different profile version.
Use the factory whenever you need a custom auth profile or want one object that drives both runtime validation and UI behavior:
import { DefaultAuthContractConfig, assertContractCompatibility, createAuthContracts } from '@anarchitects/auth-ts';
assertContractCompatibility(DefaultAuthContractConfig, '1.0.0');
const contracts = createAuthContracts(DefaultAuthContractConfig);
contracts.registerRequestSchema;
contracts.registerFormMeta.name.required;
contracts.changePasswordFormMeta.newPassword.minLength;
Custom profiles are built by copying the default profile and overriding only the fields you want to change:
import { type AuthContractConfig, DefaultAuthContractConfig, createAuthContracts } from '@anarchitects/auth-ts';
const productProfile: AuthContractConfig = {
...DefaultAuthContractConfig,
version: '1.0.0',
register: {
...DefaultAuthContractConfig.register,
name: {
...DefaultAuthContractConfig.register.name,
required: true,
minLength: 3,
emptyStringPolicy: 'strip',
},
},
login: {
...DefaultAuthContractConfig.login,
password: {
...DefaultAuthContractConfig.login.password,
minLength: 10,
},
},
};
const contracts = createAuthContracts(productProfile);
The static DTO schema exports such as RegisterRequestSchema and LoginRequestSchema remain available as default-profile convenience helpers. When a consumer needs profile customization, switch from those fixed exports to createAuthContracts(...).
Usage
Validating DTO payloads
import { Value } from '@sinclair/typebox/value';
import { LoginRequestSchema, LoginRequestDTO } from '@anarchitects/auth-ts/dtos';
const payload: LoginRequestDTO = {
credential: 'user@example.com',
password: 'secret123',
};
const errors = [...Value.Errors(LoginRequestSchema, payload)];
if (errors.length > 0) {
// handle validation error details
}
Note: TypeBox does not ship built-in email validation. Register the format once in your runtime (for example, during app bootstrap):
import { FormatRegistry } from '@sinclair/typebox'; FormatRegistry.Set('email', (value: unknown): value is string => { return typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); });
Working with domain models
import { AuthUser, Role, Permission } from '@anarchitects/auth-ts';
function can(user: AuthUser, action: string, subject: string): boolean {
const roles: Role[] = user.roles ?? [];
return roles.some((role) => (role.permissions ?? []).some((permission: Permission) => permission.action === action && permission.subject === subject));
}
The models include timestamps (createdAt, updatedAt) and bidirectional relationships to support dynamic RBAC composition in persistence layers.
AuthUser is the preferred shared auth-domain user contract. User remains exported as a temporary compatibility alias for existing consumers.
AuthUser migration
- Preferred import moving forward:
import { AuthUser } from '@anarchitects/auth-ts'or@anarchitects/auth-ts/models - Temporary compatibility alias:
Userremains exported and is type-equivalent toAuthUser - Downstream migration path: rename local imports, annotations, and helper signatures from
UsertoAuthUser; no DTO field names or runtime payload shapes changed as part of this rename - Source compatibility note: payloads such as
/auth/mestill use the field nameuser; the rename only changes the preferred shared TypeScript contract name behind that field - Database migration note: none in
@anarchitects/auth-ts; this package only ships shared contracts and runtime validation helpers
Contract profile versioning
- Contract profiles carry their own
versionfield. - Compatibility checks are exact today:
assertContractCompatibility(...)compares the expected and actual version strings directly. - Treat stricter default-profile changes as breaking changes. Examples: raising
minLength, loweringmaxLength, or changing a field from optional to required. - Consumers should pin the profile version they expect and update intentionally when contract constraints change.
Scripts
nx build auth-ts— build the distributable package with Vite.nx test auth-ts— run the Vitest suite (DTO schema checks and type-level assertions).
Development notes
- Keep auth domain models and policy semantics here; public HTTP DTO authority belongs to Contracts, with existing exports retained during transition.
- Keep the root DTO surface session-first. JWT token DTOs belong under
@anarchitects/auth-ts/dtos/jwt, not@anarchitects/auth-ts/dtos. - Use
parsePolicyRuleDTO(...)/parsePolicyRuleArrayDTO(...)when authorization rules cross trust boundaries and need runtime validation. - When changing DTO schemas, regenerate OpenAPI in the workspace (
nx run api-specs:generate). - Keep framework-specific concerns out of this package; Angular/Nest behavior belongs in domain libraries.
Snapshot test workflow
- Default-profile auth contract schemas are snapshotted in
src/contracts/auth-contracts.factory.spec.ts. - Snapshot artifacts are stored next to the spec in
src/contracts/__snapshots__/auth-contracts.factory.spec.ts.snap. - Run snapshot checks with
yarn nx run auth-ts:test. - Update snapshots intentionally with
yarn nx run auth-ts:test -- -u. - Treat snapshot diffs as contract changes: review them in pull requests and confirm every schema diff is expected.
Contract profile versioning
AuthContractConfigincludes a requiredversionfield.- Use
assertContractCompatibility(config, expectedVersion)from@anarchitects/auth-ts(or/contracts) to fail fast on profile mismatches. - The default profile version follows semver.
- Bump the major version when any default profile constraint becomes stricter (for example, higher minLength, lower maxLength, or turning an optional field into required).
Contributing
Auth DTOs are maintained in this package and consumed by Nest presentation routes. Update these schemas first, then regenerate OpenAPI via nx run api-specs:generate so every stack stays in sync.
License
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this package except in compliance with the License. You may obtain a copy of the License at the linked address or in the repository's LICENSE file. Unless required by applicable law or agreed to in writing, distributed code is provided on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Canonical version and verification
This source line consumes @anarchitects/auth-contracts@0.1.0 through
the domain TS compatibility bindings. See the canonical conformance guide
for exact checks, configured-host obligations and remaining capability gates.
Generated implementation OpenAPI and passing fixtures do not approve a deployed
host's security profile. Existing runtime support and TypeBox exports remain.