# Tutorial: Add Authentication

## Content

This tutorial guides developers through modifying the server and client code to implement authentication and permission management. You will:

* Log in with a username and password to obtain a token
* Use the token to join the chat room
* Send messages based on role permissions (admin, member, guest)

## Prerequisites

* Complete the [build real-time chat room](/spreadjs/docs/v18/spreadjs-collaboration-server/collaboration-framework/js-collaboration/tutorial-real-time-chat-room)
* Install the additional package:

    ```bash
    npm install jsonwebtoken
    ```

## Step 1: Modify the Server to Support Authentication

We will add a login route and authentication middleware to the server, ensuring that only authenticated users can join the chat room and controlling message sending based on role permissions.
**Update** `server.js`
Replace the original content of `server.js` with the following code:

```javascript
import express from "express";
import { createServer } from "http";
import { Server } from "@mescius/js-collaboration";
import jwt from "jsonwebtoken";

const app = express();
const httpServer = createServer(app);
const server = new Server({ httpServer });

app.use(express.static("public"));
app.use(express.json());

// JWT secret key
const JWT_SECRET = "your-secret-key-here";

// Simulated user database
const users = new Map([
  ["admin1", { username: "admin1", password: "admin123", role: "admin" }],
  ["member1", { username: "member1", password: "member123", role: "member" }],
  ["guest1", { username: "guest1", password: "guest123", role: "guest" }],
]);

// Login route
app.post("/login", (req, res) => {
  const { username, password } = req.body;
  const user = users.get(username);
  if (!user || user.password !== password) {
    return res.status(401).json({ success: false, message: "Invaild username or passward." });
  }
  const token = jwt.sign({ username: user.username, role: user.role }, JWT_SECRET);
  res.json({ success: true, token, user: { username: user.username, role: user.role } });
});

// Authentication middleware
server.use("connect", async (context, next) => {
  const token = context.connection.auth?.token;
  if (!token) return await next("No token provided");
  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    context.connection.tags.set("user", { username: decoded.username, role: decoded.role });
    await next();
  } catch {
    await next("Invalid token");
  }
});

// Message permission validation middleware
server.use("message", async ({ connection }, next) => {
  const user = connection.tags.get("user");
  if (user.role === "admin" || user.role === "member") await next();
  else await next("No permission to send messages");
});

// Event handlers
server.on("connect", ({ connection }) => {
  const user = connection.tags.get("user");
  console.log(`${user.username} joined the room`);
  connection.broadcast(`${user.username} joined the chat room`, "", true);
});

server.on("message", ({ connection, data }) => {
  const user = connection.tags.get("user");
  console.log(`Received message from ${user.username}: ${data}`);
  connection.broadcast(`${user.username}: ${data}`, "", true);
});

server.on("disconnect", ({ connection }) => {
  const user = connection.tags.get("user");
  console.log(`${user.username} left the room`);
  connection.broadcast(`${user.username} left the chat room`, "", true);
});

httpServer.listen(8080, () => {
  console.log("Server running at http://localhost:8080");
});
```

**Code Explanation**

* **Role Definitions**:
    * admin: Highest privileges, can send messages and manage the chat room.
    * member: Regular users, can send and receive messages.
    * guest: Can only receive messages.
* **Authentication**: The `/login` route is used to obtain a token, and the `connect` middleware verifies the token and stores user information.
* **Permissions**: The `message` middleware restricts `guest` users from sending messages.

## Step 2: Modify the Client to Support Authentication

The client will add a login interface, and users will use the token to connect to the chat room after logging in.

1. Update `public/index.html`
    Replace the content with the following code to add a login form:

    ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Real-Time Chat Room (with Authentication)</title>
      <link rel="stylesheet" href="./styles.css">
      <script type="module" src="./client.bundle.js" defer></script>
    </head>
    <body>
      <div class="container">
        <h1>Real-Time Chat Room (with Authentication)</h1>
        <div id="login">
          <input type="text" id="username" placeholder="Username">
          <input type="password" id="password" placeholder="Password">
          <button onclick="login()">Login</button>
        </div>
        <div id="chat" style="display: none;"></div>
        <div class="input-area" style="display: none;">
          <input type="text" id="message" placeholder="Enter message...">
          <button onclick="sendMessage()">Send</button>
        </div>
      </div>
    </body>
    </html>
    ```
2. Update `public/client.js`
    Replace the content with the following code to add login and authentication logic:

    ```javascript
    import { Client } from "@mescius/js-collaboration-client";
    
    let connection = null;
    
    window.login = async function () {
      const username = document.getElementById("username").value;
      const password = document.getElementById("password").value;
      try {
        const response = await fetch("http://localhost:8080/login", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ username, password }),
        });
        const result = await response.json();
        if (result.success) {
          connectToChat(result.token);
          document.getElementById("login").style.display = "none";
          document.getElementById("chat").style.display = "block";
          document.querySelector(".input-area").style.display = "flex";
        } else {
          alert(result.message);
        }
      } catch (error) {
        alert("Login failed: " + error.message);
      }
    };
    
    function connectToChat(token) {
      const client = new Client();
      connection = client.connect("chatroom", { auth: { token } });
    
      connection.on("message", (data) => {
        const chatDiv = document.getElementById("chat");
        const message = document.createElement("p");
        message.textContent = data;
        chatDiv.appendChild(message);
        chatDiv.scrollTop = chatDiv.scrollHeight;
      });
    
      connection.on("error", (error) => {
        alert("Error: " + error.message);
      });
    }
    
    window.sendMessage = function () {
      const input = document.getElementById("message");
      const message = input.value.trim();
      if (message && connection) {
        connection.send(message);
        input.value = "";
      }
    };
    ```

    **Code Explanation**
    1. **Login**: Obtain a token via the `/login` endpoint, and connect to the chat room upon successful login.
    2. **Interface**: Show the chat area and hide the login form upon successful login.
    3. **Messages**: Only messages from `admin` and `member` users will be broadcast by the server.
3. Update `public/styles.css`
    Replace the content with the following code:

    ```css
    html, body {
      height: 100%;
      margin: 0;
      font-family: Arial, sans-serif;
      background: #f0f2f5;
    }
    
    .container {
      height: 100vh;
      display: flex;
      flex-direction: column;
      background: #fff;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }
    
    h1 {
      padding: 15px;
      margin: 0;
      font-size: 24px;
      color: #333;
      text-align: center;
      border-bottom: 1px solid #ddd;
    }
    
    #login {
      padding: 20px;
      text-align: center;
    }
    
    #login input {
      padding: 10px;
      margin: 5px;
      border: 1px solid #ccc;
      border-radius: 5px;
      font-size: 14px;
    }
    
    #chat {
      flex: 1;
      overflow-y: auto;
      padding: 15px;
      background: #fafafa;
    }
    
    #chat p {
      margin: 10px 0;
      padding: 10px;
      background: #e9ecef;
      border-radius: 5px;
      word-wrap: break-word;
    }
    
    .input-area {
      display: flex;
      padding: 15px;
      border-top: 1px solid #ddd;
      background: #f9f9f9;
    }
    
    #message {
      flex: 1;
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 5px;
      font-size: 14px;
      margin-right: 10px;
    }
    
    button {
      padding: 10px 20px;
      background: #007bff;
      color: #fff;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }
    
    button:hover {
      background: #0056b3;
    }
    ```

## Step 3: Run and Test

1. Bundle the Client Code

    ```bash
    npm run build
    ```
2. Start the Server

    ```bash
    npm run start
    ```
3. Test the Functionality
    1. Visit *http://localhost:8080*
    2. Log in using the following users:
        1. admin1 / admin123 (can send messages)
        2. member1 / member123 (can send messages)
        3. guest1 / guest123 (cannot send messages)
    3. Verify the real-time functionality of multiple users joining, sending messages, and leaving.