Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NESTJS Gateway / Websocket - how to send jwt access_token through socket.emit

I am using the default passport jwt AuthGuard for my project. That works for my post & get routes fine when setting the authentication header.

Now I want to use Nestjs Gateways aswell with socket.io on the client-side, but I dont know how to send the access_token to the gateway?

That is basicly my Gateway:

@WebSocketGateway()
export class UserGateway {

  entityManager = getManager();

  @UseGuards(AuthGuard('jwt'))
  @SubscribeMessage('getUserList')
  async handleMessage(client: any, payload: any) {
    const results = await this.entityManager.find(UserEntity);
    console.log(results);
    return this.entityToClientUser(results);
  }

And on the client Im sending like this:

   this.socket.emit('getUserList', users => {
          console.log(users);
          this.userListSub.next(users);
        });

How and where do I add the jwt access_token? The documentation of nestjs misses that point completely for Websockets. All they say is, that the Guards work exactly the same for websockets as they do for post / get etc. See here

like image 248
xDrago Avatar asked Nov 02 '19 11:11

xDrago


3 Answers

For anyone looking for a solution. Here it is:

  @UseGuards(WsGuard)
  @SubscribeMessage('yourRoute')
  async saveUser(socket: Socket, data: any) {
    let auth_token = socket.handshake.headers.authorization;
    // get the token itself without "Bearer"
    auth_token = auth_token.split(' ')[1];
  }

On the client side you add the authorization header like this:

this.socketOptions = {
   transportOptions: {
     polling: {
       extraHeaders: {
         Authorization: 'your token', //'Bearer h93t4293t49jt34j9rferek...'
       }
     }
   }
};
...
this.socket = io.connect('http://localhost:4200/', this.socketOptions);
...

Afterwards you have access to the token on every request serverside like in the example.

Here also the WsGuard I implemented.

@Injectable()
export class WsGuard implements CanActivate {

  constructor(private userService: UserService) {
  }

  canActivate(
    context: any,
  ): boolean | any | Promise<boolean | any> | Observable<boolean | any> {
    const bearerToken = context.args[0].handshake.headers.authorization.split(' ')[1];
    try {
      const decoded = jwt.verify(bearerToken, jwtConstants.secret) as any;
      return new Promise((resolve, reject) => {
        return this.userService.findByUsername(decoded.username).then(user => {
          if (user) {
            resolve(user);
          } else {
            reject(false);
          }
        });

      });
    } catch (ex) {
      console.log(ex);
      return false;
    }
  }
}

I simply check if I can find a user with the username from the decoded token in my database with my user service. I am sure you could make this implementation cleaner, but it works.

like image 118
xDrago Avatar answered Oct 23 '22 09:10

xDrago


While the question is answered, I want to point out the Guard is not usable to prevent unauthorized users from establishing a connection.

It's only usable to guard specific events.

The handleConnection method of a class annotated with @WebSocketGateway is called before canActivate of your Guard.

I end up using something like this in my Gateway class:

  async handleConnection(client: Socket) {
    const payload = this.authService.verify(
      client.handshake.headers.authorization,
    );
    const user = await this.usersService.findOne(payload.userId);
    
    !user && client.disconnect();
  }
like image 36
arminfro Avatar answered Oct 23 '22 10:10

arminfro


Thanks! At the end i implemented a Guard that like the jwt guard puts the user inside the request. At the end i'm using the query string method from the socket client to pass the auth token This is my implementation:

import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { AuthService } from '../auth/auth.service';
import { User } from '../auth/entity/user.entity';

@Injectable()
export class WsJwtGuard implements CanActivate {
  private logger: Logger = new Logger(WsJwtGuard.name);

  constructor(private authService: AuthService) { }

  async canActivate(context: ExecutionContext): Promise<boolean> {

    try {
      const client: Socket = context.switchToWs().getClient<Socket>();
      const authToken: string = client.handshake?.query?.token;
      const user: User = await this.authService.verifyUser(authToken);
      client.join(`house_${user?.house?.id}`);
      context.switchToHttp().getRequest().user = user

      return Boolean(user);
    } catch (err) {
      throw new WsException(err.message);
    }
  }
}
like image 9
Felice Caricati Avatar answered Oct 23 '22 10:10

Felice Caricati