Join our Newsletter — 33% off our NHI Course
Home› Guides› NestJS Security Guide: Guards, Validation Pipes and Authentication…
Guide Application Security

NestJS Security Guide: Guards, Validation Pipes and Authentication Done Right

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 13 min read
On this page

In a NestJS API, authorisation and input validation live in guards and pipes, and a route that misses either one is open by default unless the application is built to deny. NestJS is a popular framework for enterprise TypeScript back ends, and its decorator model makes security easy to declare but also easy to forget on one controller, one GraphQL field resolver or one WebSocket gateway. This guide explains how the request pipeline enforces identity and access, how to configure ValidationPipe, serialisation and security headers, how to handle API keys and service tokens, and what to log. It is written for developers, application security engineers and reviewers of NestJS services, and draws on the official NestJS documentation as checked on 27 October 2026.

Key takeaways

  • Make authentication and authorisation global and deny by default. Register guards with APP_GUARD and mark open routes explicitly, rather than adding @UseGuards() controller by controller.
  • Guards run after middleware and before pipes and interceptors. Put identity checks in guards (they see the ExecutionContext and handler metadata), not in middleware that cannot see which handler will run.
  • Turn on whitelist and forbidNonWhitelisted. ValidationPipe without them passes unexpected properties straight into services and ORMs, which is how mass assignment happens.
  • Check object ownership in the service, record by record. A role guard on the route answers "may this user call refund"; only the service can answer "may this user refund this order".
  • Cover every transport. Global guards behave differently for hybrid microservices, GraphQL field resolvers and WebSocket connection handshakes; each needs its own check.

How identity and access work in NestJS

A NestJS request passes through middleware, then guards, then interceptors, then pipes, then the handler, then exception filters on the way out. The guards documentation states that guards execute after all middleware but before any interceptor or pipe. That order matters: a guard can read route metadata such as roles or a public flag through the Reflector, whereas middleware is context-blind and does not know which handler will execute after it calls next().

ComponentSecurity jobScope optionsTypical mistake
MiddlewareSecurity headers, CSRF origin checks, request loggingModule or app levelDoing authorisation here, where handler metadata is not visible
Guard (CanActivate)Authentication and route-level authorisation, rate limitingMethod, controller, global (useGlobalGuards or APP_GUARD)Applied per controller and forgotten on a new one
PipeValidation and transformation of body, query and paramsParameter, method, controller, globalValidationPipe without whitelist; nested DTOs not validated
InterceptorResponse serialisation, audit logging, timeoutsMethod, controller, globalReturning ORM entities with password hashes or internal fields
Exception filterSafe error responsesMethod, controller, globalCustom filters that echo stack traces or database errors

Global registration and its limits

  • APP_GUARD over useGlobalGuards: a guard registered with app.useGlobalGuards() is created outside any module and cannot inject dependencies. Registering it as a provider with the APP_GUARD token allows injection. The documentation notes that APP_GUARD can be registered several times and every registered guard runs for each request, in registration order, so put authentication before authorisation.
  • Hybrid applications: in a hybrid application, useGlobalGuards() does not set up guards for connected microservices by default. Microservice guards should throw RpcException rather than HttpException.
  • GraphQL: Nest runs global guards and interceptors on root resolvers only. Field resolvers that expose sensitive data need fieldResolverEnhancers configured, or their own checks.
  • WebSockets: Nest runs no guards on a gateway's handleConnection(), so the handshake must be authenticated separately; message handlers can then use guards.

Authentication options

Two patterns are documented. The long-standing one uses @nestjs/passport with a strategy package such as passport-jwt, plus @nestjs/jwt; the Passport recipe sets ignoreExpiration: false so expired tokens are rejected with 401, and warns that the signing key must be protected in a vault, environment variable or configuration service. The current documentation also describes a first-party @nestjs/authentication package that registers a global guard so every route requires a signed-in user unless marked @Public(), hashes passwords with scrypt from node:crypto at OWASP's recommended parameters (N=2^17, r=8, p=1), issues a __Host-sid session cookie that is HttpOnly, Secure and SameSite=Lax, and stores only hashes of session and refresh tokens. Check which pattern and version your codebase uses before applying either set of defaults.

  • JWT verification: verify signature, exp, iss and aud. The authentication package requires a signing secret of at least 32 bytes for HS256, recommends an ES256 key when other services verify the tokens, and requires both issuer and audience when accepting tokens from an external identity provider, because an IdP signs every client's tokens with the same keys.
  • Refresh tokens: opaque, stored as hashes, single use, with reuse of a spent token revoking the whole token family.
  • Roles in tokens: keep facts that change, such as roles, out of long-lived token claims and load the current user on each request.

For the token design questions behind these choices, see the OWASP API Top 10 API2 Broken Authentication Guide.

Admin roles and privileged access

The classic NestJS pattern is a RolesGuard that reads a @Roles() decorator with Reflector and compares it with request.user.roles. It works, but it fails open in one important way: a handler with no @Roles() metadata is usually allowed. The @nestjs/authorization package documented alongside the authentication package reverses this. Its AuthorizationGuard denies by default: a handler runs only when it declares a check with @Can(), @Can.Anyone() or @Public(), and the module logs at start-up every handler it will deny, which gives a ready-made migration list.

  • Function level: admin routes (refunds, user management, feature flags) need an explicit permission check on the route. OWASP ASVS 5.0 V8.2.1 expects function-level access to be restricted to consumers with explicit permissions. See the OWASP API Top 10 API5 Broken Function Level Authorization Guide.
  • Object level: checks that depend on the record (this order belongs to this customer, this refund is under this staff member's limit) belong in the service after the record is loaded, and list queries should filter by owner in the query itself. ASVS V8.2.2 covers data-specific access. See the OWASP API Top 10 API1 Broken Object Level Authorization Guide.
  • Step-up for sensitive changes: require a verified second factor for actions such as changing an account's email address, and rotate the session on privilege changes.
  • Separate admin surfaces: consider serving admin controllers from a separate application or route prefix with its own network controls, so a guard mistake is not the only barrier.

Validation, mass assignment and serialisation

SettingEffectRecommended
whitelist: trueStrips properties without validation decoratorsOn, globally
forbidNonWhitelisted: trueRejects the request instead of strippingOn for APIs, so clients learn the contract and attacks are visible in logs
forbidUnknownValues: trueFails validation of unknown objects immediatelyOn
transform: trueConverts payloads to DTO instances and primitives to declared typesOn, with explicit DTO types; or use ParseIntPipe and similar per parameter
disableErrorMessages: trueHides validation detail from clientsConsider in production for public APIs; keep detail in logs
ClassSerializerInterceptor with @Exclude()Removes marked fields from responsesOn for entities; the handler must return a class instance, not a plain object
  • Mass assignment: never pass a request DTO straight into an ORM save() or update(); map allowed fields explicitly. ASVS V15.3.3 asks for allowed fields per controller and action. See the OWASP API Top 10 API3 Broken Object Property Level Authorization Guide.
  • Nested objects: class-validator does not validate nested DTOs unless they are marked with @ValidateNested() and a @Type() decorator, so nested payloads are a common gap.
  • Schema validation: Zod and other Standard Schema libraries are supported; a Zod z.object() strips unknown properties in the same way as whitelist.
  • Response shaping: return DTOs designed for output, or use StandardSchemaSerializerInterceptor, so a new database column is not exposed by accident.
  • Errors: the built-in global exception filter returns a generic 500 "Internal server error" for unrecognised exceptions, and an error passed as cause to HttpException is not serialised. Custom filters should keep that behaviour.

Integrations, API tokens and service accounts

NestJS services are often called by other services, partner systems and scheduled jobs. These callers are non-human identities and need the same rigour as users.

  • API keys: the authentication package's documented design is a useful benchmark: a key of the form prefix, public id and 256-bit secret; only a SHA-256 hash stored; constant-time comparison with the same work for unknown ids; shown once at creation; expiry after a year; keys that cannot create or revoke other keys; and a key never counting as a second factor. A recognisable prefix lets secret scanners find leaked keys. See the API Key Management Guide.
  • Service-to-service calls: prefer short-lived tokens from an identity provider (OAuth client credentials or workload identity) verified against a JWKS URL over shared static secrets. Pin iss and aud.
  • Microservice transports: message-based handlers (Kafka, RabbitMQ, NATS, gRPC) are not protected by HTTP guards in hybrid apps unless configured; authenticate the transport (mTLS, broker ACLs) and validate message payloads with pipes.
  • Secrets in configuration: @nestjs/config loads environment variables through dotenv-style parsing, and its validationSchema applies only to environment variables, not to custom configuration files. Keep JWT secrets, database credentials and encryption keys in a secrets manager and validate required values at start-up so a missing secret fails closed.
  • Outbound integrations: HTTP clients that call partner APIs carry credentials too; scope them, rotate them and keep them out of logs. See the Secrets Management Guide.

Logging and detection

Log at the guard and service layer, where the identity and the decision are both known. The authentication package publishes sign-ins, sign-outs, second-factor changes, reused refresh tokens, password resets and email verifications on an AuthenticationEvents stream and on node:diagnostics_channel, and the authorisation package emits events for its decisions; send them to the SIEM.

EventWhy it mattersAlert idea
401 and 403 responses by principal and routeEnumeration and broken access probingSpike of 403s from one principal across many object IDs
Refresh token reuseToken theft signalAny occurrence
Repeated second-factor failuresAttacker holds the passwordLockouts per account
API key use from new IP rangesLeaked integration credentialFirst use from a new network or country
Validation rejections (forbidNonWhitelisted)Mass assignment attemptsUnexpected fields such as role or isAdmin
Throttler limits hitBrute force or scrapingSustained limits on sign-in routes

Never log tokens, API keys, password reset links or full request bodies. The authentication documentation warns that its development mail transport writes every link to the log, where anyone who reads the logs could sign in as the customer.

Metrics that show the controls are working:

  • Percentage of routes covered by a global deny-by-default authorisation guard (start-up deny list trending to zero unexplained entries).
  • Number of routes marked public, reviewed each release.
  • Validation rejection rate and the share of DTOs with explicit output shapes.
  • API keys past expiry or unused for 90 days still active.
  • Mean time to revoke a leaked integration credential.

Hardening baseline

  1. Global guards first: register authentication, then authorisation, then ThrottlerGuard with APP_GUARD. If you adopt @nestjs/authorization, import AuthenticationModule before AuthorizationModule, as the documentation requires.
  2. Global ValidationPipe: whitelist, forbidNonWhitelisted, forbidUnknownValues and transform on.
  3. Security headers: from Nest v12.1, app.useSecurityHeaders() applies Helmet 8 defaults on Express and Fastify, removes X-Powered-By and must be called before app.init() or app.listen(); older versions use the Helmet middleware.
  4. CSRF for cookie sessions: app.enableCsrfProtection() rejects cross-origin writes using Sec-Fetch-Site and Origin; configure trustedOrigins and call it right after NestFactory.create().
  5. CORS: call enableCors() with an explicit origin list and an explicit methods list, because the Express and Fastify packages have different defaults.
  6. Rate limiting: @nestjs/throttler with shared storage such as Redis when running more than one instance, and correct trust proxy settings. The documentation warns that on recent Fastify versions a numeric trustProxy hop count trusts no proxy, so every client behind the proxy is counted as one.
  7. Serialisation: ClassSerializerInterceptor or schema serialisers globally; output DTOs for sensitive entities.
  8. Dependencies and runtime: lock files, dependency review and current Node.js. See the Node.js Security Guide and the TypeScript and JavaScript Secure Coding Guide.

Rolling it out: on an existing service, add the global authorisation guard in a log-only mode first (or read the start-up deny list), mark each route explicitly, then switch to enforce in a staging ring. Enable whitelist before forbidNonWhitelisted, watch stripped-field logs for a release, then reject. Roll back by removing the provider registration, which is a one-line change, rather than editing controllers.

Choosing an approach: small teams starting fresh gain most from the first-party authentication and authorisation packages because the safe defaults are built in. Large estates with Passport strategies, external IdPs and policy engines may keep Passport for authentication and add a deny-by-default guard that calls a central policy decision point. See the OpenID AuthZEN Authorization API Guide.

Framework mapping

ControlOWASP ASVS 5.0OWASP API Top 10 2023NIST SP 800-53 Rev. 5
Global guards, deny by defaultV8.2.1API5 Broken Function Level AuthorizationAC-3, AC-6
Ownership checks in servicesV8.2.2API1 Broken Object Level AuthorizationAC-3
ValidationPipe whitelist, output DTOsV2.2.1, V15.3.3API3 Broken Object Property Level AuthorizationSI-10
Token verification, session cookiesV7.2.1API2 Broken AuthenticationSC-23
Throttlern/aAPI4 Unrestricted Resource ConsumptionSC-5
Authentication and authorisation eventsn/an/aAU-2, AU-12

In NIST CSF 2.0 terms these sit under PR.AA-05 (access permissions enforced with least privilege) and PR.PS-04 (log records generated for monitoring). Attackers exploiting a missing guard on an internet-facing API map to MITRE ATT&CK T1190 Exploit Public-Facing Application.

Common misconfigurations

  • Per-controller guards: @UseGuards(AuthGuard) on most controllers and missing on the one added last sprint.
  • RolesGuard that allows when no roles are set: the fail-open default of the common sample pattern.
  • Trusting the route guard for object access: GET /orders/:id checks the customer role but never that the order belongs to the caller.
  • ValidationPipe without whitelist: a client sends {"role":"admin"} and the service spreads the DTO into an update.
  • Unprotected side doors: GraphQL field resolvers, WebSocket handshakes and microservice handlers left outside the HTTP guard chain.
  • Static JWT secrets in .env committed to the repository, or tokens accepted from an external IdP without an audience check.
  • In-memory throttler on a multi-instance deployment, so each instance counts separately and a restart resets the count.

Practitioner checklist

  • Register authentication and authorisation guards globally with APP_GUARD, in that order, and deny routes that declare no check.
  • Review every @Public() route each release.
  • Enforce object ownership in services and filter list queries by owner.
  • Enable ValidationPipe whitelist, forbidNonWhitelisted and forbidUnknownValues globally, and validate nested DTOs.
  • Return output DTOs or excluded fields; never return raw ORM entities.
  • Verify JWT signature, expiry, issuer and audience; keep signing keys in a secrets manager.
  • Issue hashed, prefixed, expiring API keys to integrations and scan for their prefix.
  • Secure GraphQL field resolvers, WebSocket handshakes and microservice handlers separately.
  • Enable security headers, CSRF origin checks, explicit CORS and a shared-store throttler.
  • Stream authentication and authorisation events to the SIEM without logging secrets.

Standards and references

Related NHI Mgmt Group resources: API Security Guide · Node.js Security Guide · Express.js Security Guide · API Key Management Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org