nestjs/nest · error · UnauthorizedException

Unauthorized

Error message

Unauthorized

What it means

NestJS's UnauthorizedException (HTTP 401), thrown by AuthService.signIn at sample/19-auth-jwt/src/auth/auth.service.ts:15 when the credential check `user?.password !== pass` fails. Because optional-chaining is used, BOTH cases collapse into the same 401: a non-existent user (findOne returns undefined so user?.password is undefined) and a wrong password. The built-in exception is caught by NestJS's global exception filter and serialised as { statusCode: 401, message: 'Unauthorized', error: 'Unauthorized' }.

Source

Thrown at sample/19-auth-jwt/src/auth/auth.service.ts:15

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
  ) {}

  async signIn(username: string, pass: string) {
    const user = await this.usersService.findOne(username);
    if (user?.password !== pass) {
      throw new UnauthorizedException();
    }
    const payload = { username: user.username, sub: user.userId };
    return {
      access_token: await this.jwtService.signAsync(payload),
    };
  }
}

View on GitHub (pinned to 6ec0e2783d)

Solutions

  1. Confirm the user exists in the data source the app points at (check UsersService.findOne and the underlying DB/fixture) before assuming a wrong password.
  2. Verify the password comparison strategy: if the store keeps hashes, compare with bcrypt.compare(pass, user.password) rather than a raw !== check.
  3. Ensure test/dev seed scripts run before the request (e.g., the sample's UsersService seed).
  4. Make the failure explicit: split the null-user case from the wrong-password case, or log the username for debugging (never log the password).

Example fix

// before
const user = await this.usersService.findOne(username);
if (user?.password !== pass) {
  throw new UnauthorizedException();
}

// after
const user = await this.usersService.findOne(username);
if (!user || !(await bcrypt.compare(pass, user.password))) {
  throw new UnauthorizedException('Invalid credentials');
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the request shape before calling signIn
function isSignInDto(v: unknown): v is { username: string; password: string } {
  return typeof v === 'object' && v !== null
    && typeof (v as any).username === 'string' && (v as any).username.length > 0
    && typeof (v as any).password === 'string' && (v as any).password.length > 0;
}

if (!isSignInDto(body)) {
  throw new BadRequestException('username and password are required');
}
// Preferably: apply a DTO + global ValidationPipe at the controller boundary.

Type guard

function isUser(u: unknown): u is { username: string; userId: string; password: string } {
  return typeof u === 'object' && u !== null
    && typeof (u as any).username === 'string'
    && typeof (u as any).userId === 'string';
}

Try / catch

try {
  const token = await authService.signIn(username, password);
} catch (e) {
  if (e instanceof UnauthorizedException) {
    // 401 — return generic 'invalid credentials', never reveal which part failed
    return reply.code(401).send({ message: 'Invalid credentials' });
  }
  throw e;
}

Prevention

When it happens

Trigger: POST to the auth/login route (wired to signIn) with a username not present in the backing store, or with a password that differs from the stored value. Also fires when UsersService.findOne returns undefined because seed/fixture users were never loaded.

Common situations: Fresh dev DB without seeded users; plaintext-vs-hashed mismatch (e.g., DB stores a bcrypt hash but the code compares it verbatim to a plaintext input); frontend posting the wrong field name; e2e/unit tests that skip seeding; case-sensitivity differences in username.

Related errors


AI-assisted analysis of nestjs/nest@6ec0e2783d (2026-08-03). Data as JSON: /data/errors/47adb12df3eeb148.json. Report an issue: GitHub.