socket.ionestjsnestjs-gateways

NestJs Socket io adapter: Server does not add CORS headers to handshake's response if allowFunction is called with false. Bug or misconfiguration?


authenticated-socket-io.adapter.ts

export class AuthenticatedSocketIoAdapter extends IoAdapter {
  private readonly authService: AuthService;

  constructor(private app: INestApplicationContext) {
    super(app);
    this.authService = this.app.get(AuthService);
  }

  createIOServer(port: number, options?: SocketIO.ServerOptions): any {
    options.allowRequest = async (request, allowFunction) => {
      const { authorized, errorMessage } = await this.check(parse(request?.headers?.cookie || '').jwt, [UserRole.ADMIN]);
      if (!authorized) {
        return allowFunction(errorMessage, false);
      }

      return allowFunction(null, true);
    };


    return super.createIOServer(port, options);
  }

main.ts

const app = await NestFactory.create(AppModule);
  app.enableCors({
    origin: ['http://localhost:4200'],
    credentials: true
  });
  app.use(cookieParser());
  app.use(csurf({ cookie: true }));
  app.useWebSocketAdapter(new AuthenticatedSocketIoAdapter(app));

When authorization is successful: authorization is successful

When authorization fails: authorization fails


Solution

  • Found the problem: the function from socket io handling the error message:

    /**
     * Sends an Engine.IO Error Message
     *
     * @param {http.ServerResponse} response
     * @param {code} error code
     * @api private
     */
    
    function sendErrorMessage (req, res, code) {
      var headers = { 'Content-Type': 'application/json' };
    
      var isForbidden = !Server.errorMessages.hasOwnProperty(code);
      if (isForbidden) {
        res.writeHead(403, headers);
        res.end(JSON.stringify({
          code: Server.errors.FORBIDDEN,
          message: code || Server.errorMessages[Server.errors.FORBIDDEN]
        }));
        return;
      }
      if (req.headers.origin) {
        headers['Access-Control-Allow-Credentials'] = 'true';
        headers['Access-Control-Allow-Origin'] = req.headers.origin;
      } else {
        headers['Access-Control-Allow-Origin'] = '*';
      }
      if (res !== undefined) {
        res.writeHead(400, headers);
        res.end(JSON.stringify({
          code: code,
          message: Server.errorMessages[code]
        }));
      }
    }
    

    here the "code" param will be the one I pass in here "allowFunction(errorMessage, false)" That value has to be one of "0", "1", "2", "3" or "4", otherwise the isForbidden will be false, thus not setting the 'Access-Control-Allow-Credentials' header.

    Server.errorMessages = {
      0: 'Transport unknown',
      1: 'Session ID unknown',
      2: 'Bad handshake method',
      3: 'Bad request',
      4: 'Forbidden'
    };
    

    Hope this helps someone one day.