Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/abdelhafid37/talkbox/llms.txt

Use this file to discover all available pages before exploring further.

TalkBox uses JSON Web Tokens (JWT) for stateless authentication. When a user registers or logs in, the server issues a signed token that the client stores locally and attaches to every subsequent request — both REST calls and Socket.IO connections. A <ProtectedRoute> component on the client side ensures that only authenticated users can access the chat interface.

Registration

New accounts are created by posting user credentials to the registration endpoint. The server validates all fields, checks for duplicates, hashes the password with bcrypt, and persists the new user to MongoDB. Endpoint: POST /api/auth/register Request body:
{
  "username": "alice",
  "email": "alice@example.com",
  "password": "supersecret"
}
Success response — 201 Created:
{
  "message": "User registered successfully."
}
Validation rules enforced by the server:
  • username must be a string between 3 and 30 characters (whitespace is trimmed before checking).
  • email must match the pattern /^[a-zA-Z0-9\.]{2,}@[a-zA-Z]{2,}\.[a-zA-Z]{2,}$/.
  • password must be at least 8 characters long.
  • A duplicate username or email returns 409 Conflict with a message identifying which field is already taken.

Password hashing

Passwords are never stored in plain text. The server hashes them with bcrypt at 10 salt rounds before writing to the database:
const SALT_ROUNDS = 10;
const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS);

await User.create({
  username: trimmedUsername,
  email: trimmedEmail,
  password: hashedPassword,
});

Login

Existing users authenticate with their email and password. On success, the server returns a signed JWT valid for 7 days. Endpoint: POST /api/auth/login Request body:
{
  "email": "alice@example.com",
  "password": "supersecret"
}
Success response — 200 OK:
{
  "token": "<signed-jwt>"
}
The token is signed with JWT_SECRET from the server environment. Its payload contains a single claim:
{
  "userId": "<mongodb-object-id>",
  "iat": 1700000000,
  "exp": 1700604800
}
const token = jwt.sign({ userId: user.id }, JWT_SECRET, {
  expiresIn: "7d",
});

Token storage and usage (client)

1

Persist the token

After a successful login the client stores the raw JWT string in localStorage under the key "token":
function setToken(token) {
  setTokenState(token);
  localStorage.setItem("token", token);
}
On page reload, AuthProvider initialises its token state directly from localStorage:
const [token, setTokenState] = useState(() => localStorage.getItem("token"));
2

Attach the token to REST requests

Every protected REST call includes the token as an Authorization: Bearer header. The message service is a representative example:
async function getConversation(userId) {
  const token = localStorage.getItem("token");
  const response = await api.get(`/messages/${userId}`, {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return response.data;
}
3

Pass the token to Socket.IO

Before connecting the socket, AuthProvider sets socket.auth.token so the server middleware can verify it during the handshake:
socket.auth = { token };
socket.connect();

socket.on("connect", () => {
  socket.emit("join");
});

Protected routes (client)

The <ProtectedRoute> component wraps any route that requires authentication. It reads the current token from AuthContext and redirects unauthenticated visitors to /login:
function ProtectedRoute({ children }) {
  const { token } = useAuth();

  if (!token) {
    return <Navigate to="/login" replace />;
  }

  return children;
}
The /chat route is wrapped with this component in the router configuration, so users who are not logged in are automatically sent to the login page.

JWT middleware (server)

Every protected API route is guarded by authMiddleware. It reads the Authorization header, verifies the token against JWT_SECRET, and attaches the decoded payload to req.user for downstream controllers:
function authMiddleware(req, res, next) {
  try {
    const authorization = req.headers.authorization;

    if (!authorization || !authorization.startsWith("Bearer ")) {
      return res.status(401).json({ message: "Unauthorized." });
    }

    const token = authorization.split(" ")[1];

    const JWT_SECRET = process.env.JWT_SECRET;
    const payload = jwt.verify(token, JWT_SECRET);

    req.user = {
      userId: payload.userId,
    };

    next();
  } catch (error) {
    return res.status(401).json({ message: "Unauthorized." });
  }
}
After the middleware runs, any controller in the chain can read req.user.userId to identify the acting user — for example, messageController uses it to set the sender field when creating a new message. Socket.IO connections go through an equivalent middleware that reads socket.handshake.auth.token and attaches the full User document to socket.user for the duration of the session.

Making an authenticated request

The example below shows how to fetch a conversation history using the JWT token retrieved from localStorage:
const token = localStorage.getItem("token");

const response = await fetch(`/api/messages/${otherUserId}`, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
});

if (!response.ok) {
  throw new Error("Unauthorized or request failed.");
}

const messages = await response.json();

Logout

Logging out clears the token from both React state and localStorage, and disconnects the socket:
function logout() {
  localStorage.removeItem("token");

  setTokenState(null);
  setUser(null);
  setOnlineUsers([]);
}
Once token becomes null, the AuthProvider effect calls socket.disconnect() and the <ProtectedRoute> guard redirects the user back to /login on the next render.

Build docs developers (and LLMs) love