Anarchitecture Bricks Docs

Repository documentation hub for packages, guides, and generated references.

@anarchitects/auth-nest

NestJS services, controllers, and infrastructure for the Anarchitecture authentication domain. This package wires contract-driven DTOs from @anarchitects/auth-ts, uses Better Auth as the canonical internal auth engine, keeps email/password always enabled, and layers repo-owned RBAC on top of Better Auth-backed user/session state.

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

Features

Installation

The commands below target the next minor after coordinated publication. See the verified Nest 11/12 matrix and install recipes and release plan for current npm versions, migrations and maintenance routing. These runtime packages work in ordinary npm/Yarn/pnpm Nest applications without Nx, the Nest CLI or @anarchitects/nest.

The Nest 11/12 contract below is prepared for the unreleased Auth 0.12 minor, with Auth declarations/TS ^0.12.0 and Common Mailer ^0.5.0. Published Auth 0.11.x remains the Nest 11 maintenance line. Common 0.5 must be released before Auth 0.12; do not apply these dependency changes as a 0.11.x patch.

npm install @anarchitects/auth-nest@^0.12.0 @nestjs/common@^12.1.2 @nestjs/core@^12.1.2 @nestjs/platform-fastify@^12.1.2 @nestjs/config@^12.0.1 @nestjs/jwt@^12.0.2 @nestjs/typeorm@^12.0.2 typeorm@^1.1.0 @nestjs-modules/mailer@^3.0.2 nodemailer@^8.0.5
# or
yarn add @anarchitects/auth-nest@^0.12.0 @nestjs/common@^12.1.2 @nestjs/core@^12.1.2 @nestjs/platform-fastify@^12.1.2 @nestjs/config@^12.0.1 @nestjs/jwt@^12.0.2 @nestjs/typeorm@^12.0.2 typeorm@^1.1.0 @nestjs-modules/mailer@^3.0.2 nodemailer@^8.0.5

Peer requirements:

The internal @anarchitects/auth-ts and @anarchitects/common-nest-mailer packages are installed transitively. The published community package @anarchitects/better-auth-typeorm-adapter is also installed transitively and used internally by @anarchitects/auth-nest; consumers do not need to wire it directly when using this package facade. Runtime utilities such as @casl/ability, bcrypt, better-auth, and @better-auth/passkey are direct dependencies of this package. Common Mailer's Mailer 3/Nodemailer 8 peers are required even when Auth selects noop; noop avoids transport setup and delivery, not installation of the mandatory dependency graph.

Nest 11/12 compatibility (#464)

The supported peers are common/core/platform-fastify ^11.1.6 || ^12.1.2, Config ^4.0.2 || ^12.0.1, JWT ^11.0.1 || ^12.0.2, Nest TypeORM ^11.0.1 || ^12.0.2, and TypeORM ^1.1.0. Keep common/core/platform/testing on the same major. The older integration peers exclude Nest 12 themselves.

Consumer Nest Config JWT Nest TypeORM Fastify
nest11 11.1.6 4.0.2 11.0.1 11.0.1 5.4.0
nest11-modern 11.1.6 12.0.1 12.0.2 12.0.2 5.12.5
nest12 12.1.2 12.0.1 12.0.2 12.0.2 5.12.5

All three use TypeORM 1.1.0, PostgreSQL 16, the published Better Auth TypeORM adapter 0.2.0, Better Auth/core/passkey 1.7.2, Mailer 3.0.2 and Nodemailer 8.0.5. Better Auth and passkey are pinned alongside core: their patch releases can change exact upstream peer requirements, so update the family together and refresh the consumer locks. Passport is not part of the current Auth runtime; JWT uses the package's Better Auth integration and @nestjs/jwt directly.

Breaking upgrade requirements: Auth 0.12 drops Node 20 because its locked Kysely 0.29.6 requires Node >=22. Use the verified Node 22.13.0, 24.11.0 or 26.11.1 lanes. The next minor requires Common Mailer 0.5's Mailer 3/Nodemailer 8 stack and the newer Auth TS/declaration packages. Keep the 0.11.x line for hosts that retain the older stack. Public facade/secondary entry points, metadata keys and CJS/ESM formats are preserved. Auth TS and declarations now emit explicit .js relative specifiers so strict NodeNext consumers can resolve their types without skipLibCheck or consumer aliases.

Nest 12 Fastify bootstrap requirement: Nest 12.1.2 classifies errors more strictly; Fastify's ordinary schema-validation Error otherwise becomes HTTP 500. Configure the host adapter to produce a Nest HTTP exception. This works on Nest 11 too and preserves HTTP 400 for invalid Auth request schemas:

import { BadRequestException } from '@nestjs/common';
import { FastifyAdapter } from '@nestjs/platform-fastify';

const adapter = new FastifyAdapter({
  schemaErrorFormatter: (errors, dataVar) => new BadRequestException(`${dataVar} ${errors.map((error) => error.message).join(', ')}`),
});
// Pass adapter to NestFactory.create<NestFastifyApplication>(AppModule, adapter).

Run the uncached packed-consumer checks with Docker available:

yarn nx run auth-nest:test-nest-compatibility:nest11
yarn nx run auth-nest:test-nest-compatibility:nest11-modern
yarn nx run auth-nest:test-nest-compatibility:nest12

The target builds real artifacts, verifies source/build/pack contracts, and installs their tarballs outside the workspace using locked registry dependencies, strict peer/engine checks and no install scripts. tools/testing/auth-nest-hosts/candidates.json rehearses only the upcoming package versions (Auth 0.12/Common 0.5); it does not rewrite dependency ranges or peer metadata. Checked-in source versions and publication remain owned by the release workflow. After release, update that candidate map and locks together. To refresh a lock intentionally after building, run node tools/testing/run-auth-nest-host.mjs <consumer> --refresh-lock, then run the normal Nx targets. Registry integrity hashes are retained; local candidate tarballs are rebuilt and their manifest contracts checked on each run.

The hosts compile decorated CJS/ESM consumers with strict NodeNext declarations, verify metadata identity and all exported Nest layers, and exercise facade, config-driven and advanced composition, global guards, CASL resource policies, Fastify request validation, real session/JWT flows, migrations, passkey persistence, restart and optional provider/route disabling. They use a disposable PostgreSQL container and noop mail delivery. No workspace symlinks or peer overrides are used. Linux packed CI passed Nest 11.1.6 and 12.1.2 on Node 22.13.0, 24.11.0 and 26.11.1 with TypeScript 6.0.3. Node 20 is excluded for Auth on both majors. TypeORM's process-wide entity registry is not treated as evidence of optional table absence.

The strict NodeNext declaration fixes are also suitable for backporting to the 0.11.x maintenance line. Published 0.11.0 has the earlier declaration layout; deprecation may be considered after a fixed maintenance release is available, but is proposed only and requires human approval. No version bump, release or deprecation is performed by #464.

Better Auth Adapter Boundary

@anarchitects/auth-nest now consumes the published @anarchitects/better-auth-typeorm-adapter package internally for Better Auth database composition.

Maintainers can validate the published npm artifact integration path with:

yarn nx run auth-nest:test-published-adapter

That target boots auth-nest against ephemeral PostgreSQL and exercises the real published adapter package through the host repo's Nest integration path. It requires Docker or another supported local container runtime because the suite provisions PostgreSQL through testcontainers.

Exports

Import path Contents
@anarchitects/auth-nest Facade-first runtime entry point: AuthModule.forRoot(...), AuthModule.forRootFromConfig(...), provideAuthRuntimeGuards(), @AuthorizedResource(), plus layered re-exports for convenience
@anarchitects/auth-nest/application AuthApplicationModule, AuthService, AuthPrincipalResolver, PoliciesService, ResourceAuthorizationService, AbilityFactory, resource-authorization helpers/types
@anarchitects/auth-nest/presentation Runtime presentation internals for advanced composition: AuthPresentationModule, AuthController, AuthenticationGuard, AuthorizationGuard, PoliciesGuard, ResourceAuthorizationGuard, provideAuthRuntimeGuards(), @AuthorizedResource()
@anarchitects/auth-nest/infrastructure-persistence AuthPersistenceModule, core auth persistence entities, CreateAuthSchema1720200000000, AuthAccountRepository, AuthUserRepository, and persistence module option types
@anarchitects/auth-nest/infrastructure-mailer AuthMailerModule, NodeMailerAdapter
@anarchitects/auth-nest/config authConfig, AuthConfig type, InjectAuthConfig()

Controller-facing security declarations that should not pull in this runtime package live in @anarchitects/auth-declarations. Use that package for @Public(), @Policies(...), and @AuthorizeResource(...). Runtime enforcement, guards, modules, principal resolution, and app wiring remain owned by @anarchitects/auth-nest.

RoutePolicy likewise belongs to the contract/declaration side. Import it from @anarchitects/auth-ts/models or use the declaration helpers from @anarchitects/auth-declarations; do not treat auth-nest as the source of truth for route metadata contracts.

Runtime Security Enforcement

auth-nest owns the runtime side of authentication and authorization.

Use the app shell to activate runtime security centrally:

import { Module } from '@nestjs/common';
import { AuthModule, provideAuthRuntimeGuards } from '@anarchitects/auth-nest';

@Module({
  imports: [AuthModule.forRoot({})],
  providers: [...provideAuthRuntimeGuards()],
})
export class AppModule {}

provideAuthRuntimeGuards() registers AuthenticationGuard first and AuthorizationGuard second as APP_GUARD providers, so principal resolution always runs before route-policy or resource checks. Controller-level UseGuards(...) wiring is no longer the default integration path.

The intended split is:

Wiring Migrations And Entities

Prefer symbol imports from @anarchitects/auth-nest/infrastructure-persistence when wiring TypeORM DataSource migrations or host-level entity registration. This gives consumers a stable public contract instead of depending on package-internal file paths.

Recommended:

import { AccountEntity, AddBetterAuthAccountIssuer1788275931000, AuthUserEntity, CreateAuthSchema1720200000000, PermissionEntity, RoleEntity, SessionEntity, VerificationEntity } from '@anarchitects/auth-nest/infrastructure-persistence';
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
  type: 'postgres',
  migrations: [CreateAuthSchema1720200000000, AddBetterAuthAccountIssuer1788275931000],
  entities: [AccountEntity, AuthUserEntity, PermissionEntity, RoleEntity, SessionEntity, VerificationEntity],
});

Legacy glob-based wiring still works, but it depends on package internals and is not the recommended integration path:

import { join } from 'node:path';

export const AppDataSource = new DataSource({
  type: 'postgres',
  migrations: [join(process.cwd(), 'node_modules/@anarchitects/auth-nest/src/infrastructure-persistence/migrations/*.js')],
});

Use the symbol-based approach for production apps and reusable templates. Keep plugin-specific migrations with their owning plugin modules. The infrastructure-persistence entry point exposes the core auth schema migration and the ordered Better Auth 1.7 account-issuer migration.

The Better Auth 1.7 and TypeORM 1.1 line requires Node.js ^22.13.0 || >=24.11.0, Better Auth 1.7.2, @anarchitects/better-auth-typeorm-adapter@0.2.0, TypeORM ^1.1.0, and @nestjs/typeorm@^11.0.1 || ^12.0.2. Read the auth migration guide before upgrading a populated database. The adapter's 0.2.0 license is Apache-2.0 rather than MIT.

AuthUser migration and compatibility

Configuration

The library reads configuration through @nestjs/config using a namespaced authConfig registered under the key auth. Set the following environment variables to customise behaviour:

Variable Description Default
AUTH_BETTER_AUTH_BASE_URL Better Auth base URL used for internal route generation. http://localhost:3000/api/auth
AUTH_BETTER_AUTH_SECRET Better Auth secret. Must be overridden in production. better-auth-secret-32-chars-minimum
AUTH_BETTER_AUTH_VERIFY_EMAIL_CALLBACK_URL App-facing callback URL embedded in verification emails. <base-url origin>/verify-email
AUTH_BETTER_AUTH_RESET_PASSWORD_CALLBACK_URL App-facing callback URL embedded in password reset emails. <base-url origin>/reset-password
AUTH_PLUGIN_JWT_ENABLED Enables the internal JWT plugin routes. false
AUTH_PLUGIN_JWT_SECRET Secret key used by the JWT plugin. Must be overridden when enabled. default_jwt_secret
AUTH_PLUGIN_JWT_EXPIRATION JWT plugin token lifetime (e.g. 3600s, 15m, 1d). 3600s
AUTH_PLUGIN_JWT_AUDIENCE Expected aud claim for JWT plugin tokens. your_audience
AUTH_PLUGIN_JWT_ISSUER Expected iss claim for JWT plugin tokens. your_issuer
AUTH_PLUGIN_PASSKEYS_ENABLED Enables the passkeys plugin. false
AUTH_PLUGIN_PASSKEY_RP_ID Passkey relying-party ID. localhost
AUTH_PLUGIN_PASSKEY_RP_NAME Passkey relying-party display name. Anarchitecture Auth
AUTH_PLUGIN_PASSKEY_ORIGIN Expected passkey browser origin. Origin of Better Auth base URL
AUTH_PLUGIN_SOCIAL_ENABLED Enables social auth plugins. false
AUTH_PLUGIN_SOCIAL_GITHUB_CLIENT_ID GitHub social sign-in client ID. unset
AUTH_PLUGIN_SOCIAL_GITHUB_CLIENT_SECRET GitHub social sign-in client secret. unset
AUTH_PLUGIN_OIDC_ENABLED Enables future OIDC plugin wiring. false
AUTH_ENCRYPTION_ALGORITHM Password hashing algorithm (bcrypt). bcrypt
AUTH_ENCRYPTION_KEY Symmetric key for additional encryption needs. Must be overridden. default_encryption_key
AUTH_MAILER_PROVIDER Domain mailer provider for forRootFromConfig(...) (node or noop). node

Security note: The defaults for AUTH_BETTER_AUTH_SECRET, AUTH_PLUGIN_JWT_SECRET, and AUTH_ENCRYPTION_KEY are intentionally insecure placeholders. Always provide strong, unique values in any deployed environment.

Injecting the config

import { InjectAuthConfig, AuthConfig } from '@anarchitects/auth-nest/config';

@Injectable()
export class MyService {
  constructor(@InjectAuthConfig() private readonly config: AuthConfig) {}

  someMethod() {
    const secret = this.config.betterAuth.secret;
  }
}

Make sure to import authConfig into your module's ConfigModule:

import { ConfigModule } from '@nestjs/config';
import { authConfig } from '@anarchitects/auth-nest/config';

@Module({
  imports: [ConfigModule.forRoot({ load: [authConfig] })],
})
export class AppModule {}

Usage

Contract-driven route validation

@anarchitects/auth-nest now resolves request-body validation from an auth contract profile at module bootstrap.

Easy mode (single facade import)

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CommonMailerModule, mailerConfig } from '@anarchitects/common-nest-mailer';
import { AuthModule, provideAuthRuntimeGuards } from '@anarchitects/auth-nest';
import { authConfig } from '@anarchitects/auth-nest/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [authConfig, mailerConfig],
    }),
    CommonMailerModule.forRootFromConfig(),
    AuthModule.forRoot({
      contracts: {
        register: {
          name: {
            required: true,
          },
        },
      },
      presentation: {
        application: {
          encryption: {
            algorithm: 'bcrypt',
            key: process.env.AUTH_ENCRYPTION_KEY!,
          },
        },
      },
      mailer: {
        provider: 'node',
      },
    }),
  ],
  providers: [...provideAuthRuntimeGuards()],
})
export class AuthApiModule {}

AuthModule.forRoot(...) is the preferred integration path when you want a full auth stack with minimal host-module wiring.

Use AuthModule.forRootFromConfig() when you want module composition fully driven by AUTH_* variables exposed via authConfig.

Apply contract-profile overrides without adding new AUTH_* variables:

AuthModule.forRootFromConfig({
  contracts: {
    register: {
      name: {
        required: true,
      },
    },
  },
});

The same contract profile drives Fastify request validation for the core auth routes:

Disable domain mailer wiring when not needed:

AuthModule.forRoot({
  presentation: { application: { ... } },
  mailer: { provider: 'noop' },
});

Layered composition (advanced)

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CommonMailerModule, mailerConfig } from '@anarchitects/common-nest-mailer';
import { authConfig } from '@anarchitects/auth-nest/config';
import { AuthApplicationModule } from '@anarchitects/auth-nest/application';
import { AuthPresentationModule, provideAuthRuntimeGuards } from '@anarchitects/auth-nest/presentation';
import { AuthMailerModule } from '@anarchitects/auth-nest/infrastructure-mailer';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [authConfig, mailerConfig],
    }),
    CommonMailerModule.forRootFromConfig(),
    AuthApplicationModule.forRoot({
      encryption: {
        algorithm: 'bcrypt',
        key: process.env.AUTH_ENCRYPTION_KEY!,
      },
    }),
    AuthPresentationModule.forRoot({
      contracts: {
        register: {
          name: {
            required: true,
          },
        },
      },
      application: {
        encryption: {
          algorithm: 'bcrypt',
          key: process.env.AUTH_ENCRYPTION_KEY!,
        },
      },
    }),
    AuthMailerModule.forRoot({
      provider: 'node',
    }),
  ],
  providers: [...provideAuthRuntimeGuards()],
})
export class AuthApiModule {}

Use layered composition when you need to replace or selectively compose infrastructure/application concerns.

Optional JWT plugin

Core auth remains session-first. Only enable the JWT plugin when the host app explicitly needs token-based routes:

AuthModule.forRoot({
  presentation: {
    application: {
      plugins: {
        jwt: {
          enabled: true,
          secret: process.env.AUTH_PLUGIN_JWT_SECRET!,
        },
      },
    },
  },
});

That mounts the plugin-owned /auth/jwt/login, /auth/jwt/logout, and /auth/jwt/refresh routes alongside the package-owned core session routes.

Optional passkey server ceremonies

See the consumer integration and contract-test guide for public composition, deployment requirements, supported routes, and failure behavior.

Enable passkeys to provide AuthPasskeyService, exported from the root and @anarchitects/auth-nest/application entry points:

AuthModule.forRoot({
  presentation: {
    application: {
      plugins: {
        passkeys: {
          enabled: true,
          rpID: 'example.com',
          rpName: 'Example',
          origin: 'https://login.example.com',
        },
      },
    },
  },
});

Advanced composition uses the same plugins.passkeys options on AuthApplicationModule.forRoot(...). Both facade and application modules also support forRootFromConfig(...) with explicit overrides taking precedence. The service is registered only when the plugin is enabled. Core AuthService and password authentication are unchanged.

Service method Contract from @anarchitects/auth-ts/dtos/passkeys Returned body
beginRegistration(dto, headers) PasskeyRegistrationBeginRequestDTO JSON creation options
finishRegistration(dto, headers) PasskeyRegistrationFinishRequestDTO { success: true }
beginAuthentication(dto, headers) PasskeyAuthenticationBeginRequestDTO JSON request options
finishAuthentication(dto, headers) PasskeyAuthenticationFinishRequestDTO Repository-owned { user, rbac }

All methods return { body, headers }. Pass incoming request cookies to each method and forward every returned Set-Cookie header. Registration requires a fresh authenticated session at both steps and binds the challenge to that user. Authentication can begin without a session and establishes one after verification. The service validates request DTOs even when called outside a controller.

The adapter calls the Better Auth passkey server APIs, which use SimpleWebAuthn for verification. Better Auth stores challenges in its verification store, correlates them with signed cookies, expires them after five minutes, and consumes them before cryptographic verification. Challenge types prevent registration/authentication substitution. Verification checks the stored challenge, configured origin and RP ID, credential signature, and stored counter; the successful counter is persisted before session creation. Zero-only counters remain supported for authenticators that do not implement a signature counter. User verification follows the plugin's preferred policy; a biometric/PIN check is not mandatory in this implementation.

Origin configuration: the expected origin defaults to the origin of betterAuth.baseUrl, never the request's Origin header. For a frontend on a different origin, explicitly set plugins.passkeys.origin (or AUTH_PLUGIN_PASSKEY_ORIGIN). It must be an HTTP(S) origin without a path, query, fragment, or credentials, and its hostname must match or be a subdomain of the RP ID. The configured origin is included in Better Auth's trusted origins. This tightens the previous implicit request-origin fallback.

The facade/presentation module mounts these opt-in POST routes when plugins.passkeys.enabled is true (200 on success):

Route Service method
/auth/passkeys/registration/begin beginRegistration
/auth/passkeys/registration/finish finishRegistration
/auth/passkeys/authentication/begin beginAuthentication
/auth/passkeys/authentication/finish finishAuthentication

They validate shared DTOs and forward challenge/session cookies. Sign-in routes are marked @Public(); enrollment remains protected by host guards and the engine's fresh-session checks. Disabled plugins expose no passkey routes. Paths above omit any host global prefix, such as /api.

Angular browser orchestration is available through @anarchitects/auth-angular/state/passkeys; see its usage guide. The server service uses the optional TypeORM passkey table through the published Better Auth adapter.

Run yarn nx run auth-nest:test-passkeys for real registration/assertion tests, including replay, expiry, wrong origin/RP ID, signature, session ownership, and counter rejection. This target also runs as a dependency of auth-nest:test.

Passkey persistence and migration

When enabling passkeys, add the plugin migrations to the host's existing core migration list. Both migrations and PasskeyEntity are public exports:

import { CreateAuthSchema1720200000000, CreateBetterAuthPasskeysTable1760200001000, AddBetterAuthAccountIssuer1788275931000, ExpandPasskeyCredentialStorage1790899200000, PasskeyEntity } from '@anarchitects/auth-nest/infrastructure-persistence';

const migrations = [CreateAuthSchema1720200000000, CreateBetterAuthPasskeysTable1760200001000, AddBetterAuthAccountIssuer1788275931000, ExpandPasskeyCredentialStorage1790899200000];

The enabled plugin registers PasskeyEntity with Nest TypeORM. Hosts using autoLoadEntities: true pick it up automatically; standalone runtime and migration DataSources must include it alongside their core auth entities. Importing the symbol alone does not enable the plugin. Use migrations with synchronize: false.

Stored credentials retain the owning auth user, credential ID, COSE public key, counter, optional label, transports, device type, backup status, AAGUID, and creation time. Credential IDs are globally unique, user lookups are indexed, and deleting an auth user cascades to that user's passkeys. No cross-domain entity relations are introduced.

The storage expansion supports the WebAuthn credential and counter limits: 1,023-byte credential IDs (1,364 base64url characters) and unsigned 32-bit counters. PostgreSQL stores the counter as bigint with a 0..4294967295 check constraint; the entity converts it to an exact JavaScript number for the verifier. Zero-only counters remain valid. Existing enrolled keys and metadata are preserved in place.

Upgrade requirement: run ExpandPasskeyCredentialStorage1790899200000 after the passkey table creation migration and before deploying this entity definition. It rejects invalid existing counters rather than repairing them silently. The original creation migration is unchanged. Rollback refuses credentials longer than the old 500-character limit or counters above the old signed-integer limit; it never truncates IDs or resets counters. See the migration guide for deployment and rollback guidance.

yarn nx run auth-nest:test-published-adapter tests this flow against disposable PostgreSQL using the public package exports, real Nest service, and published adapter. It covers populated upgrades, rollback guards, metadata retention, authentication across application restart, counter boundaries, duplicate/orphan rejection, concurrent challenge replay, and user-deletion cascade. CI already runs this target for affected auth code; Docker is required locally.

Mailer Migration Note

AuthMailerModule is now adapter-only. It wraps shared CommonMailerModule.forRoot(...) provider wiring from @anarchitects/common-nest-mailer and no longer configures transport with MailerModule.forRootAsync(...). Configure transport once at app root with CommonMailerModule. Set mailer.provider: 'noop' to disable active delivery behavior per domain. The shared mailer DI contract (MailerPort) and concrete NodeMailerAdapter now live in @anarchitects/common-nest-mailer.

Injecting services

import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from '@anarchitects/auth-nest/application';
import { LoginRequestDTO } from '@anarchitects/auth-ts/dtos';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  login(@Body() dto: LoginRequestDTO) {
    return this.authService.login(dto);
  }
}

Route-level authorization with policies

import { Controller, Patch } from '@nestjs/common';
import { AuthorizeResource, Policies } from '@anarchitects/auth-declarations';
import { AuthorizedResource } from '@anarchitects/auth-nest';

@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Patch(':postId')
  @Policies({ action: 'update', subject: 'Post' })
  @AuthorizeResource({ action: 'update', subject: 'Post', idParam: 'postId' })
  async updatePost(@AuthorizedResource() post: Post) {
    return this.postsService.update(post);
  }
}
import { AuthModule } from '@anarchitects/auth-nest';

AuthModule.forRoot({
  presentation: {
    application: {
      resourceAuthorization: {
        loaders: {
          Post: async ({ resourceId }) => postsRepository.findById(resourceId),
        },
      },
    },
  },
});

@Policies() remains the coarse route-level declaration. @AuthorizeResource(...) declares the concrete resource check that auth-nest runtime guards can perform after loading the resource through the app-registered loader. The runtime flow attaches the authorized resource to the request so @AuthorizedResource() can read it in the handler.

Authorization Model

CASL integration in @anarchitects/auth-nest is intentionally split into two layers:

Use this split to avoid overstating what route metadata can prove. Ownership-sensitive rules such as "writers may only update their own posts" need the concrete resource instance before CASL can decide correctly.

What the library enforces

What the host app must provide

The library owns authorization orchestration. The host app still owns how domain resources are found.

REST endpoints

The AuthController exposes the following routes (all prefixed with /auth):

Method Path Description
POST /auth/register Register a new user
PATCH /auth/activate Activate a user account
POST /auth/login Log in and establish a Better Auth session
POST /auth/logout Log out and clear the Better Auth session
PATCH /auth/change-password/:userId Change password for a user
POST /auth/forgot-password Request a password-reset email
POST /auth/reset-password Reset password with token
POST /auth/verify-email Verify an email address
PATCH /auth/update-email/:userId Update email for a user
GET /auth/me Get logged-in user info and RBAC rules

When the JWT plugin is enabled, these plugin-owned routes are also mounted:

Method Path Description
POST /auth/jwt/login Log in and receive JWT tokens
POST /auth/jwt/logout Invalidate JWT plugin tokens
POST /auth/jwt/refresh Refresh JWT plugin token pairs

Nx scripts

Development notes

License

Licensed under the Apache License, Version 2.0. Unless required by applicable law or agreed to in writing, software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.

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.

Source Links