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
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.
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();
}
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);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With