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
- 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.
- Verify the password comparison strategy: if the store keeps hashes, compare with bcrypt.compare(pass, user.password) rather than a raw !== check.
- Ensure test/dev seed scripts run before the request (e.g., the sample's UsersService seed).
- 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
- Always seed the user store in dev/test before running auth flows.
- Hash passwords with bcrypt and compare via bcrypt.compare, never plaintext.
- Apply a DTO with class-validator at the controller so malformed input never reaches signIn.
- Use the same error message for 'user not found' and 'wrong password' to avoid user enumeration.
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
- Unauthorized
- 3
- The "connect()" method is not supported in gRPC mode.
- Method is not supported in gRPC mode. Use ClientGrpc instead
- Method is not supported in gRPC mode.
AI-assisted analysis of nestjs/nest@6ec0e2783d (2026-08-03).
Data as JSON: /data/errors/47adb12df3eeb148.json.
Report an issue: GitHub.