Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Micro connection while validation jwt in middleware #5346

Unanswered
Bubuioc asked this question in Q&A
Discussion options

Hello i get a micro connection while validating jwt in my middleware.
Middleware itself:

export const WsAuthMiddleware = (
 socket: Socket,
 next: (err?: any) => void,
): void => {
 passport.authenticate(
 'jwt',
 { session: false },
 (err: unknown, user: IJwtSignPayload) => {
 if (err) {
 const errorResponse =
 err instanceof HttpException
 ? err.getResponse()
 : new InternalServerErrorException().getResponse();
 return next(errorResponse);
 }
 socket.handshake['user'] = user;
 next();
 },
 )(socket.request, {}, next);
};

It's using in adapter

export class SocketIOAdapter extends IoAdapter {
 private readonly configService: ConfigService;
 private readonly redisService: RedisService;
 private adapterConstructor: ReturnType<typeof createAdapter>;
 constructor(private readonly app: INestApplicationContext) {
 super(app);
 this.configService = this.app.get(ConfigService);
 this.redisService = this.app.get(RedisService);
 this._createClient();
 }
 private _createClient(): void {
 const streamName = this.configService.get<string>('redisConfig.streamName');
 const redisClient = this.redisService.getClient;
 this.adapterConstructor = createAdapter(redisClient, { streamName });
 }
 createIOServer(port: number, options?: ServerOptions): Server {
 const server: Server = super.createIOServer(port, {
 ...options,
 cors: {
 origin: (_req: any, callback: (arg0: null, arg1: boolean) => void) => {
 callback(null, true);
 },
 credentials: true,
 },
 transports: ['polling', 'websocket'],
 allowUpgrades: true,
 pingTimeout: 2000,
 pingInterval: 7000,
 });
 server.adapter(this.adapterConstructor);
 server.use(WsAuthMiddleware);
 return server;
 }
}

Is there a way to skip this? socket_connection

You must be logged in to vote

Replies: 2 comments 7 replies

Comment options

instead of header send token in query and then extract like this socket.handshake.query.token

You must be logged in to vote
2 replies
Comment options

instead of header send token in query and then extract like this socket.handshake.query.token

I do not think this is best practice to put jwt in the query. Thanks for your advice ❤️

Comment options

Either put the token in socket.handshake.auth, or in socket.handshake.headers.authorization.
I don't think taht the way oh sending token in the issue there.

Comment options

Hi! You can use io.engine.use() instead of io.use().

Reference: https://socket.io/docs/v4/middlewares/#compatibility-with-express-middleware

You must be logged in to vote
5 replies
Comment options

Hi! You can use io.engine.use() instead of io.use().

Reference: https://socket.io/docs/v4/middlewares/#compatibility-with-express-middleware

Yep, i found this in the docs but the thing is that i can not access handshake here to put the user in it

 server.engine.use((req, res, next) => {
 const isHandshake = req._query.sid === undefined;
 if (isHandshake) {
 const socket: Socket = req.socket;
 if (!socket) {
 new Logger().error('Cannot get socket in middleware');
 return next(new InternalServerErrorException());
 }
 passport.authenticate(
 'jwt',
 { session: false },
 (err: unknown, user: IJwtSignPayload) => {
 //socket.handshake is undefined
 socket.handshake['user'] = user;
 return next();
 },
 )(req, res, next);
 }
 });

I need to put user in the handshake because i can get it from the RemoteSocket type

 const sockets = await this.io.in(room).fetchSockets();
 const socket = sockets.find(({ handshake }) => {
 const wsUser: IJwtSignPayload = handshake['user'];
 if (!wsUser) throw new NotFoundException(`User not found`);

If there is a better practice where to put the user that i can get it from the remote socket too it will be super.

Comment options

I found that if i will use passport the user property will be found in the socket.request, but here is rising other question how can i give the connection error properly.
Sience i use Nest.js this is how my strategy looks:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
 constructor(
 private readonly configService: ConfigService,
 private readonly usersService: UsersService,
 ) {
 super({
 jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
 ignoreExpiration: false,
 secretOrKey: configService.get<string>('jwtConfig.access.secret'),
 });
 }
 async validate(payload: IJwtSignPayload): Promise<UsersDto> {
 const user: UsersDto = await this.usersService.validateUser(
 payload.id,
 payload.session.id,
 );
 return user;
 }
}

This is how do i use middlleware

 server.engine.use(
 (
 req: Request & { _query: { sid?: string }; socket: Socket },
 res: Response,
 next: NextFunction,
 ) => {
 const isHandshake = req._query.sid === undefined;
 if (isHandshake) {
 passport.authenticate('jwt', (err, user) => {
 if (err) {
 const logger = new Logger();
 logger.error(err);
 return next(new Error('Internal Server Error'));
 }
 if (!user) {
 return next(new Error('Unauthorized'));
 }
 req['user'] = user;
 return next();
 })(req, res, next);
 } else {
 next();
 }
 },
 );

In postman instead of unauthorize as server response i get Error: Unexpected server response: 400
if i will use in this mode

 if (isHandshake) {
 passport.authenticate('jwt')(req, res, next);
 } else {
 next();
 }

I get Error: Unexpected server response: 502

Comment options

You can put it in the data object:

server.engine.use((req, res, next) => {
 const isHandshake = req._query.sid === undefined;
 if (isHandshake) {
 passport.authenticate("jwt", { session: false })(req, res, next); // init the req.user object
 } else {
 next();
 }
});
server.on("connection", (socket) => {
 const userId = socket.request.user.id;
 socket.data.userId = userId;
});

References:

Comment options

I apologize for the spamming, @darrachequesne. I disabled nginx, and now when the JWT is invalid, the response I get is "Error: socket hang up" in the example you provided. However, when I manually handle the error, I still receive a 400 Bad Request on localhost:3000. When the JWT is valid, everything works fine, and the user is successfully assigned to the socket request. The issue now is how I can send a proper error message when the JWT is invalid.

Comment options

You can put it in the data object:

server.engine.use((req, res, next) => {
 const isHandshake = req._query.sid === undefined;
 if (isHandshake) {
 passport.authenticate("jwt", { session: false })(req, res, next); // init the req.user object
 } else {
 next();
 }
});
server.on("connection", (socket) => {
 const userId = socket.request.user.id;
 socket.data.userId = userId;
});

References:

You can even put the user in socket['user'].
This works fine in my case.

I don't think the problem is related to where you put the token ( handshale.auth, handshake.headers.authorization, ... ) or where you set the user data.
I would investigate on two things :

  • the authentication middleware. As I can see in the screenshot, there is a 401 error, that indicates a failure in it. Token not retrieved ? invalid ?
  • maybe client side ?
    a few years ago I had issues with react in strict mode. The component that handeled socket.io connexion was rendered, destroyed and rendered once again, causing an error : connexion closed before established. Looking at the screenshot, I assume that it's probably not the case here, cause the connexion is tested with postman, so no client framework involved here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /