Authenticating socket io connections using JWT - node.js

How can I authenticate a socket.io connection? My application uses a login endpoint from another server (python) to get a token, how can I get use that token whenever a user opens a socket connection on the node side?
io.on('connection', function(socket) {
socket.on('message', function(message) {
io.emit('message', message);
});
});
And the client side:
var token = sessionStorage.token;
var socket = io.connect('http://localhost:3000', {
query: 'token=' + token
});
If the token is created in python:
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
How can I use this token to authenticate a socket connection in node?

It doesn't matter if the token was created on another server. You can still verify it if you have the right secret key and algorithm.
Implementation with jsonwebtoken module
client
const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000', {
query: {token}
});
Server
const io = require('socket.io')();
const jwt = require('jsonwebtoken');
io.use(function(socket, next){
if (socket.handshake.query && socket.handshake.query.token){
jwt.verify(socket.handshake.query.token, 'SECRET_KEY', function(err, decoded) {
if (err) return next(new Error('Authentication error'));
socket.decoded = decoded;
next();
});
}
else {
next(new Error('Authentication error'));
}
})
.on('connection', function(socket) {
// Connection now authenticated to receive further events
socket.on('message', function(message) {
io.emit('message', message);
});
});
Implementation with socketio-jwt module
This module makes the authentication much easier in both client and server side. Just check out their examples.
client
const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000');
socket.on('connect', function (socket) {
socket
.on('authenticated', function () {
//do other things
})
.emit('authenticate', {token}); //send the jwt
});
Server
const io = require('socket.io')();
const socketioJwt = require('socketio-jwt');
io.sockets
.on('connection', socketioJwt.authorize({
secret: 'SECRET_KEY',
timeout: 15000 // 15 seconds to send the authentication message
})).on('authenticated', function(socket) {
//this socket is authenticated, we are good to handle more events from it.
console.log(`Hello! ${socket.decoded_token.name}`);
});

Since I don't have enough reputation to add a comment to accepted answer:
const socketioJwt = require('socketio-jwt');
Is not actually apart of auth0 's repo and is a third-party community based package.
Answer above should be updated as the link to auth0 repo is a page 404.

Here I wrote the indepth article on how to authenticate the user on the sockets and also save the user's data
https://medium.com/#tameemrafay/how-to-authenticate-user-and-store-the-data-in-sockets-19b262496feb
CLIENT SIDE CODE
import io from "socket.io-client";
const SERVER = "localhost:4000";
const socket = io(SERVER, {
auth: {
token: "2c87b3d5412551ad69aet757f81f6a73eb919e5b02467aed419f5b2a9cce2b5aZOzgaM+bpKfjdM9jvez37RTEemAp07kOvEFJ3pBzvj8="
}
});
socket.once('connect', (socketConnection) => {
console.log('socket connected', socketConnection);
})
// Emit the message and receive data from server in callback
socket.emit("user send message", {message: "Hi you there" }, callback => {
if (callback) {
console.log("--- response from server", callback);
}
});
SERVER SIDE CODE
const initializeSockets = (io) => {
io.on('connection', (socket) => {
decryptAndStoreUserData(socket,io);
}
const decryptAndStoreUserData = async (socket,io) => {
const { token } = socket.handshake.auth; // receive the token from client
// here i decypt the auth token and get the user data
const genToken = new Middlewares().token();
const userData = genToken.decryptTokenForSockets(token);
// save the data of user in socket
socket.data.user = userData;
}

Write a middleware:
const jwt = require("jsonwebtoken");
const authSocketMiddleware = (socket, next) => {
// since you are sending the token with the query
const token = socket.handshake.query?.token;
try {
const decoded = jwt.verify(token, process.env.TOKEN_SECRET_KEY);
socket.user = decoded;
} catch (err) {
return next(new Error("NOT AUTHORIZED"));
}
next();
};
module.exports = authSocketMiddleware;
Use it inside socket server
socketServer = (server) => {
const io = require("socket.io")(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
// before connection use the middleware
io.use((socket, next) => {
authSocketMiddleware(socket, next);
});
io.on("connection", (socket) => {
console.log("user connected", socket.id);
});
};

Related

Socket.io How to emit from post request and listen from same server

I want to send some data from post request to above socket.
Currently, I am doing emit to client from res.post, listen and emit again to socket from client. This is quite complicated.
Can I do it like this?
io.on("connection", (socket) => {
socket.on("frompost", (body) => {
console.log(body);
socket.emit("toclient", body);
});
});
app.post("/scan", (req, res) => {
const id = req.body.wheelId;
const token = req.body.token;
const data = {};
//VALIDATE TOKEN HERE
io.sockets.emit("frompost", data); // run listener above
});
Use this best practice for authorization in Socket if you use api for authentication validation
In socket.io there is a middleware that can be used to validate, and we will use that middleware to validate tokens.
to sent token from client side in socket.io
let host "http://localhost:3000" // here is your host
let socket = io(host, {
headers: {
Authorization: "your token"
}
})
and for server side
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server, Socket } = require('socket.io');
const jwt = require("jsonwebtoken");
// set limit of data(1e6 = 1mb)
const io = new Server(server,{
maxHttpBufferSize: 1e9
});
//socket io middleware
io.use((socket, next) => {
const { authorization } = socket.handshake.headers;
try {
socket.user = jwt.verify(authorization, process.env.SECRET_KEY);
next()
} catch (error) {
error.statusCode = 401
next(error)
}
});
When the user connects and an event occurs, this socket.io middleware checks the authentication token

NodeJS express getting hanging requests on Windows 10

I have been getting hanging requests, with only expressjs, on my windows 10 machine. The code I will post is the current version of the code; it was tried a) without async, and b) without redis anywhere and just the 'test' route. The request just hangs, whether called from Postman or accessed directly from a browser. Admin privileges were given with the same result.
const express = require('express')
import { createClient } from "redis"
import { UserApi } from "./api/users"
(async () => {
const app = express()
const client = createClient()
//Check redis
client.on('error', (err) => console.log('Redis Client Error', err))
//Connect to redis client
await client.connect()
//Create API instances
const ua = new UserApi(client)
//Initialize middleware\
app.use(express.json)
//Create routes
//test
app.get("/test", function(req, res) {
res.send("got")
})
//signup - creates user
app.post("/signup", async function(req, res) {
console.log("starting")
const u = {username: req.body.username, email: req.body.email, password: req.body.password}
console.log("why")
try {
console.log("trying")
const token = await ua.create(u)
res.send(token)
} catch (error) {
res.status(400).send(error)
}
})
app.listen(3000, () => {
console.log("server up")
})
}) ()

How to organize socket-io files in an express server?

I have recently started using socket-io for a live-chatting feature in a project of mine. I have everything working fine but as of now, I have all the server side socket-io stuff (connection, middleware, event handlers, etc.) in the main "index.js" file. It isn't a big deal now as I am only listening to a couple of events, but I would like to organize and separate the code into smaller files before it gets out of hand.
Here is an example of what the socket-io portion of the code looks like in my index.js file:
const express = require("express");
const app = express();
const http = require("http");
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
const jwt = require("jsonwebtoken");
const activeSockets = {};
io.use((socket, next) => {
const { token } = socket.handshake.auth;
if (!token) return next(new Error("Invalid or missing token"));
const { _id } = jwt.verify(token, process.env.JWT_KEY);
socket.handshake.auth._id = _id;
next();
});
const addSocket = (socket) => {
const { _id } = socket.handshake.auth;
if (!activeSockets[_id]) activeSockets[_id] = [socket.id];
else activeSockets[_id] = [...activeSockets[_id], socket.id];
};
const removeSocket = (socket) => {
const { _id } = socket.handshake.auth;
if (!_id || !activeSockets[_id]) return;
const index = activeSockets[_id].indexOf(socket.id);
activeSockets[_id].splice(index, 1);
if (activeSockets[_id].length < 1) delete activeSockets[_id];
};
io.on("connection", (socket) => {
addSocket(socket);
socket.on("typing", (isTyping, recipients, conversation, sender) => {
recipients.forEach((recipient) => {
if (activeSockets[recipient._id]) {
activeSockets[recipient._id].forEach((r) => {
socket.to(r).emit("typing", isTyping, conversation, sender);
});
}
});
});
socket.on("sendMessage", ({ message, recipients, conversation }) => {
recipients.forEach((recipient) => {
if (activeSockets[recipient._id]) {
activeSockets[recipient._id].forEach((r) => {
socket.to(r).emit("receiveMessage", { message, conversation });
});
}
});
});
socket.on("disconnect", () => {
removeSocket(socket);
});
});
const port = process.env.PORT || 5000;
server.listen(port, () => {
console.log("Listening: ", port);
});
I'm just struggling to find an efficient way to extract the socket-io code into smaller more organized pieces. Should the only thing in index.js related to socket-io be the connection itself? And then I have files for different event handlers that take an "io" parameter and then I call "io.on(...)" in those external functions? Or perhaps should I listen for all the events in index.js and then extract only the logic of each event into separate files? Something like:
io.on("eventName", someExternalFunction)
This is my first experience with socket-io so I'm not too sure of the "best practices".
Thank you to anyone who can offer help!
You could put the socket event handlers into modules like.
chat/connection.js:
module.exports = (io) => {
io.on("connection", (socket) => {
console.log('connection was made');
});
}
Then in index.js require('./chat/connection.js')(io);

socket.io connects with same socket id

my socket creates problem when frontend loads before the server,
My problems are
1.I get same the socketid from the cookies of multiple clients
2.I get only one client who is connected with multiple socketids from the server
3.When I get this problem, my API calls will not work and I won't get any data from my database
I also get this problem when I restart the server, and when I refresh the frontend multiple times with different clients
my server side code
const mongoose = require("mongoose");
express = require("express");
app = express();
bodyParser = require("body-parser");
cookieParser = require("cookie-parser");
cors = require("cors");
user = require("./routes/user");
message = require("./routes/message");
http = require("http");
server = http.createServer(app);
io = require("socket.io")(server);
var userdata = require("./controllers/user");
mongoose
.connect(process.env.DATABASE, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
})
.then(() => {
console.log("DB CONNECTED");
})
.catch((err) => console.log(err));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(cors());
app.use(express.json());
app.use("/use", user);
app.use("/use", message);
let users = [];
io.on("connection", (socket) => {
socket.on("done", () => {
let userdata = require("./controllers/user");
console.log("connected");
userdata.userdata &&
users.push({ userid: userdata.userdata._id, socketid: socket.id });
console.log(users);
});
socket.broadcast.emit("message");
socket.on("more", function (c) {
console.log(c.a, c.b);
let d = users.find((s) => s.userid === c.b);
if (d) {
return io.to(d.socketid).emit("message", c);
}
});
socket.on("disconnect", () => {
console.log(socket.id);
if (users) {
for (let e = 0; users.length; e++) {
if (users[e] && users[e].socketid === socket.id) {
return users.splice(e, 1);
}
}
}
console.log(users);
return console.log("disconnected");
});
});
// app.use();
const port = process.env.PORT || 8000;
server.listen(port, () => {
console.log(`app is running at ${port}`);
});
I found that my problem is caused by userdata, when I had deleted everything related to userdata, I didn't get any problem even when the server is reloaded.
Here userdata comes from a middileware called isSignedIn,this middleware is called before every API call from this webpage, so userdata gets updated frequently by the frontend code.This is my isSignedIn function
exports.isSignedIn = async (req, res, next) => {
const header = req.headers["authorization"];
const token = header && header.split(" ")[1];
if (!token) return res.json("no token");
jwt.verify(token, "jsdhbcjsd", (err, User) => {
if (err) return res.json(`${err} not signedin`);
req.User = User;
exports.userdata = User;
next();
});
};
I tried to call isSignedIn() instead of importing userdata, which would be lot better, but I was getting an error from the headers, so I couldn't call this function.
error I get when I call this function isSignedIn()
Promise {
<rejected> TypeError: Cannot read property 'headers' of undefined
at exports.isSignedIn (D:\message\backend\controllers\user.js:86:22)
it tells about this line
const header = req.headers["authorization"];
I made sure that the socket gets connected in the frontend only after calling the APIs using await,so that the userdata gets updated before connecting to the socket.I had tested it in the console,socket gets connected only after calling APIs
async componentDidMount() {
//my API calls
await this.friends(token);
await this.findfriends(token);
//connect the socket
this.start();
this.recieve();
}}
My frontend code
const client = require("socket.io-client");
var socket
export default class Home extends Component {
constructor(props) {
super(props);
this.start = this.start.bind(this);
this.send = this.send.bind(this);
this.recieve = this.recieve.bind(this);
this.friends= this.friends.bind(this);
this.findfriends= this.findfriends.bind(this);
}
start(){
socket=client("http://localhost:8000");
}
send(){
socket.emit("more", c)
}
recieve(){
socket.on("message", c)
}
async componentDidMount() {
//my API calls
await this.friends(token);
await this.findfriends(token);
//connect the socket
this.start();
this.recieve();
}}
render(){
return(my data)
}
}
After thinking for a while about requesting headers,which isn't possible, I thought, why couldn't I get userid from the socket when just it gets connected, then I tried this code, it worked perfectly fine
client side
start = () => {
socket = client("http://localhost:8000");
socket.on("connect", () => {
return socket.emit("userinfo", this.state.User._id);
});
};
server side
socket.on("userinfo", function (user) {
users.push({ userid: user, socketid: socket.id });
console.log("C O N N E C T E D");
});
You can't reassign exports.userdata = User; in middleware. That will affect every single request that uses those exports so they will all end up looking at the same userdata, no matter which user they are. That's the source of your confusion. There's only one exports object for each module and everyone who uses that module sees the same exports object. So, you can't use exports for request-specific data.
I see you are already assigning req.User = User. That is an appropriate place to put request-specific data and other users of that data in the processing of the request should get the data from req.User, not from the exported object. That will keep the data separate for each request and each user.

Socket.io in server gets disconnect repeatedly while on client side doesn't

I am going to make a private chat app like WhatsApp.
I connect to the server successfully
but the socket after several seconds gets disconnect from the server.
while on the client it doesn't disconnect.
Server code:
const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const port = 3000;
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
const onlineusers = {};
const socketid = {};
io.on('connection', cs => {
cs.on('online', username => {
if(username){
onlineusers[username] = cs.id;
socketid[cs.id] = username;
}
console.log("\nonline: ", onlineusers);
});
cs.on('disconnect', () => {
delete onlineusers[socketid[cs.id]];
console.log("\noffline: ", onlineusers);
});
});
const chat = io.of("/chat");
chat.on('connection', cs => {
cs.on('startchat', username => {
if (username){
chat.to('/chat#'+onlineusers[username]).emit('hey', 'I love programming');
}
});
});
server.listen(port, err => {
if(err){
console.error("Some Error: "+err);
}else{
console.log(`Server is running on port: ${port}`);
}
});
MY CLIENT code is by react-native and socket.io-client:
On line users file:
import io from 'socket.io-client';
const SocketEndpoint = 'http://192.168.43.172:3000';
this.socket = io(SocketEndpoint, {
transports: ['websocket']
});
this.socket.on('connect', () => {
if (this.state.username) {
this.socket.emit("online", this.state.username);
}
});
this.socket.on('connect_error', (err) => {
Alert.alert(err);
});
this.socket.on('disconnect', () => {
Alert.alert('disconnected');
});
Chat page file:
import io from 'socket.io-client';
const SocketEndpoint = 'http://192.168.43.172:3000/chat';
this.socket = io(SocketEndpoint, {
transports: ['websocket']
});
this.socket.on('connect', () => {
if (theirusername) {
this.socket.emit('startchat', theirusername);
}
this.socket.on('hey', data => {
alert(data);
});
this.socket.on('janajan', data => {
alert(data);
});
});
I want to keep to client socket on the server until the client themselves gets the disconnect.
because here when I want to say hey it gets a disconnect and my message could pass to the client.
thank you before

Resources