nestjsnestjs-passportnestjs-jwt

UnauthorizedException is deliver as Internal Server Error


I'm trying a create a shared Guard as an external library in order to be imported and used across services. I'm not doing anything special that what is described in some guides but with the particularity that the code will reside in a shared library. Everything is working but the Exception to return a 401 error.

My guard looks something like this:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class MainGuard extends AuthGuard('jwt') {}

Nothing else. If I use that in a service folder it works, but at the time that I move as in their own library, the response changes.

The way that I'm using in the service has nothing special:

import { MainGuard } from 'shared-guard-library';
import { Controller, Get, UseGuards } from '@nestjs/common';
import { SomeService } from './some.service';

@Controller()
export class SomeController {
  constructor(private someService: SomeService) {}

  @Get('/foo')
  @UseGuards(MainGuard)
  async getSomething(): Promise<any> {
    return this.someService.getSomething();
  }
}

The client receives an error 500:

http :3010/foo
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Content-Length: 52
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Dec 2021 04:11:42 GMT
ETag: W/"34-rlKccw1E+/fV8niQk4oFitDfPro"
Keep-Alive: timeout=5
Vary: Origin
X-Powered-By: Express

{
    "message": "Internal server error",
    "statusCode": 500
}

And in the logs shows:

[Nest] 93664  - 12/08/2021, 10:11:42 PM   ERROR [ExceptionsHandler] Unauthorized
UnauthorizedException: Unauthorized
    at MainGuard.handleRequest (/sharedGuardLibrary/node_modules/@nestjs/passport/dist/auth.guard.js:68:30)
    at /sharedGuardLibrary/node_modules/@nestjs/passport/dist/auth.guard.js:49:128
    at /sharedGuardLibrary/node_modules/@nestjs/passport/dist/auth.guard.js:86:24
    at allFailed (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:101:18)
    at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:174:28)
    at Object.strategy.fail (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:296:9)
    at Object.JwtStrategy.authenticate (/sharedGuardLibrary/node_modules/passport-jwt/lib/strategy.js:96:21)
    at attempt (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:360:16)
    at authenticate (/sharedGuardLibrary/node_modules/passport/lib/middleware/authenticate.js:361:7)
    at /sharedGuardLibrary/node_modules/@nestjs/passport/dist/auth.guard.js:91:3

The logs are telling me that the correct exception was thrown, but is ignored at some point and I don't know the reason. Again: the same code in the same project works.

I took a look at the original class and I don't see any particular way to treat the exception

Any clue or guide it will appreciate.


Solution

  • So, this happens to be a "feature" of Typescript and how JavaScript object equality works in general. So in Nest's BaseExceptionFilter there's a check that exception instanceof HttpException, and normally, UnauthorizedException would be an instance of this, but because this is a library there's a few things that need to be considered.

    1. All of the NestJS dependencies you're using have to be peerDependencies. This makes sure that when the library is installed, there's only one resulting package for the @nestjs/* package.

    2. during local development, you'll need to take care to ensure that you're not resolving multiple instances of the same package (even if it's the exact same version, to JavaScript { hello: 'world' } === { hello: 'world' } // false). To take care of this, things like npm/yarn/pnpm link should not be used, but instead you should copy the dist and the package.json to the main application's node_modules/<package_name> directory.

      a. The other option is using a monorepo tool like Nest's monorepo approach or Nx which have single package version approaches, and use the paths of the libraries rather than internal links.

    If you follow this, when your production application installs the npm library, everything will work without an issue. It's an annoyance for sure, but it's a side effect of how JavaScript works