Sending socketio messages via the browser console in Flask

Question:
I am making a chat site in which messages are sent from the client to the server and from the server to the clients using socket io. But because of this, anyone will be able to send messages to the server using the browser console. How can this be fixed?

I use Flask to start the server.

Repl link: https://replit.com/@KAlexK/Chatapp?v=1

Code:

app.py (excerpts)

from flask import Flask, request, render_template, redirect, url_for, flash
from flask_socketio import send

import os

from db_manager import DataBaseManager
import events_manager as events


app = Flask(__name__)
app.config["SECRET_KEY"] = os.urandom(32)

events.socketio.init_app(app)

db = DataBaseManager("Chat.db")


@app.route("/", methods=["GET", "POST"])
def index():
    if current_user.is_authenticated:
        posts = db.posts_all(20)[::-1]
        form = PostForm()
        if form.validate_on_submit():
            db.post_create(current_user.get_username(), form.text.data)
        return render_template(
            "index_login.html", user=current_user, posts=posts, form=form
        )
    return render_template("index_no_login.html")


@app.route("/send", methods=["POST"])
def index_send_message():
    form = PostForm()
    if form.validate_on_submit():
        db.post_create(current_user.get_username(), form.text.data)
        return redirect(url_for("index"))


if __name__ == "__main__":
    events.run(app)

events_manager.py

from flask_socketio import SocketIO, emit

from db_manager import DataBaseManager

socketio = SocketIO()

db = DataBaseManager("Chat.db")


@socketio.on("connect")
def connect_event():
  print("Client connected")


@socketio.on("new_message")
def message_event(data):
  db.post_create(data["poster"], data["text"])
  emit("new_message", data, broadcast=True)
  print(f"New message: {data}")


def run(app):
  socketio.run(app, host="0.0.0.0", port=2200)

index_login.html

{% extends 'base.html' %}

{% block title %}Title{% endblock %}

{% block content %}

Welcome to chat, <span id="username">{{ user.get_username() }}</span>!

<hr>

<div>
    <p>Write a message.</p>
    <textarea id="message_text"></textarea><br>
    <button id="send_message">Send</button>
</div>

<hr>

{% if posts %}
    {% for post in posts %}
        <div class="message">
            <strong>{{ post[1] }}</strong>
            <p>{{ post[2] }}</p>
        </div>
    {% endfor %}
{% else %}
    <p>There is nothing in this chat yet.</p>
{% endif %}

<script src="{{ url_for('static', filename='index_login.js') }}"></script>

{% endblock %}

index_login.js

const socket = io();
socket.on("connect", function(data) {
    console.log("Socket.IO is connected!!!")
});

const body = document.body;

const send_message = document.getElementById("send_message");

send_message.addEventListener("click", function(){
    const message_text = document.getElementById("message_text");
    const username = document.getElementById("username");
    socket.emit("new_message", {poster: username.innerHTML, text: message_text.value});
    console.log("new_message");
});

socket.on("new_message", function(data) {
    console.log(`new_message recived ${data}`);
    const messages = body.getElementsByClassName("message");
    const new_message = document.createElement("div");
    new_message.className = "message";
    const user = document.createElement("strong");
    user.innerHTML = data.poster;
    new_message.append(user);
    const text = document.createElement("p");
    text.innerHTML = data.text;
    new_message.append(text);
    messages[0].before(new_message);
});
1 Like

You can either use session management or socket authentication.

If you use session management Flask has a package called Flask-Session to manage sessions.

If you use socket authentication you can generate a JWT for the user so he can emit a socket and have your server validate the thing.

3 Likes

This won’t work because someone can manually generate the JWT token in the same way it was originally created.

1 Like

AFAIK the JWT use a secret key to produce the signature. So even if someone manually generate the JWT they won’t be able to produce a valid signature without knowing the secret key. If the signature doesn’t match what the server expects, the token is considered tampered with and the server rejects it.

(Of course, as long as your secret key is secured)

2 Likes

Do you realize that people can automate the browser using playwright or undetected_chromedriver? Therefore, it could produce a valid signature.

2 Likes

Well I see no reason why would someone reach such lenghts to just get a key from a recently made webchat (no offense Alex) but if we are going to talk about security exploits we will talk all day about how we can implement captchas, rate limiting, behaviour analysis, heuristic check, honeypots until we exaust every single security option :melting_face:

I mean, I think JWT is fine just for a layered protection. If the project advance to something further maybe start to try another thing too. Session Management have security problems like JWT have too.

1 Like

Honestly, a signature is a better option. While JWT is good, it relies on how well keys are managed and protected, which isn’t always ideal. Signatures are more commonly employed in data verification, secure communication protocols, digital signatures, and so on. They don’t have a standardized format for carrying claims or structured data, unlike JWT tokens. Yes, creating signatures can be more challenging for various reasons, but in the end, it would be worth it if you want robust protection.

However, as you mentioned, I don’t think it really matters in this scenario. This is just a small project (no offense, please; don’t take it the wrong way). So, low-level protection should work fine. Even just hashing the given WebSocket data and checking if it matches server-side is good enough, even though it could easily be reversed. It’s okay because it works at the end of the day.

1 Like

Thank you for all the tips you wrote here, @Sky and @WindLother . But I didn’t quite understand what JWT and signatures are. Could you give a definitive answer to my question and (if possible) some code?

1 Like

A simple solution for your case would be to make use of scope. If all your socket code is local, it cannot be directly accessed in the browser console. This is far from secure, but I think this is all you’re trying to do.

I would recommend modularising your code using ES6 modules, however you could also use IIFEs.

To make a script a module, just add type="module" in the HTML. Modules have local scope, so variables are not exposed to the developer console. You can also use the ES6 import statement in modules.

IIFE stands for Immediately Involed Function Expression. An IIFE works by creating it’s own local scope using a function, and then immediately calling that function. An IIFE looks like this:

(function () {
    // your code here
})();
2 Likes

But if the module has a local visibility zone, how can I transfer a variable from Python to a JavaScript file? Now I’m using this part of the html code:

<script>
    room = {{ {"data": room}|tojson }}.data
</script>

To make that a module it just becomes:

<script type="module">
    room = {{ {"data": room}|tojson }}.data
</script>

But you will want to keep most things together in one script, and you may need to import the socket.io library.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.