# สัปดาห์ที่ 10.2 - CORS (Cross-Origin Resource Sharing)

## บทนำ

**CORS** คือเทคนิคความปลอดภัยของเบราว์เซอร์ที่ควบคุมว่าเพจ HTML จากต้นทางหนึ่ง (origin) สามารถเข้าถึงทรัพยากรจากต้นทางอื่น (origin) ได้หรือไม่ โดยไม่มี CORS headers ที่ถูกต้อง เบราว์เซอร์จะปฏิเสธการเข้าถึงข้อมูล

---

## 1. Origin คืออะไร?

**Origin** ประกอบด้วย:

- **Protocol**: `http://` หรือ `https://`
- **Domain**: `example.com`
- **Port**: `:3000`, `:8080` เป็นต้น

### ตัวอย่าง Origins:

```
https://example.com        (Port default: 443 สำหรับ HTTPS)
http://localhost:3000      (Port 3000)
https://api.example.com    (Subdomain ต่างกัน = origin ต่างกัน)
```

### ตัวอย่าง Same-Origin vs Cross-Origin

```
Current Page: https://myapp.com/page

✅ Same-Origin:
   https://myapp.com/api/data
   https://myapp.com/users

❌ Cross-Origin:
   https://api.example.com/data      (Domain ต่างกัน)
   http://myapp.com/data              (Protocol ต่างกัน)
   https://myapp.com:8080/data       (Port ต่างกัน)
```

---

## 2. Same-Origin Policy (SOP)

**Same-Origin Policy** คือนโยบายความปลอดภัยที่ป้องกัน scripts จากหน้าที่หนึ่งเข้าถึงข้อมูลจากหน้าอื่นที่แตกต่างกัน

### ตัวอย่าง: CORS Error

```javascript
// เพจอยู่ที่: https://myapp.com
// พยายามดึงข้อมูลจาก: https://api.example.com

fetch("https://api.example.com/users")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error(error));
```

**ผลลัพธ์ใน Console**:

```
Access to XMLHttpRequest at 'https://api.example.com/users'
from origin 'https://myapp.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
```

---

## 3. CORS Headers - ฝั่งเซิร์ฟเวอร์

เพื่ออนุญาตให้ client จากต้นทางอื่นเข้าถึง เซิร์ฟเวอร์ต้องส่ง CORS headers:

### ตัวอย่าง: Express.js Server

```javascript
// ติดตั้ง: npm install cors
const express = require("express");
const cors = require("cors");
const app = express();

// ✅ อนุญาตให้ทุก origins เข้าถึง
app.use(cors());

// ❌ หรือระบุเฉพาะ origins ที่อนุญาต
app.use(
  cors({
    origin: ["https://myapp.com", "https://localhost:3000"],
    methods: ["GET", "POST", "PUT", "DELETE"],
    credentials: true,
  }),
);

app.get("/api/users", (req, res) => {
  res.json({ message: "Users data" });
});

app.listen(5000);
```

### CORS Headers ที่สำคัญ

| Header                             | ความหมาย                                                      |
| ---------------------------------- | ------------------------------------------------------------- |
| `Access-Control-Allow-Origin`      | ต้นทางที่อนุญาตให้เข้าถึง (เช่น `https://myapp.com` หรือ `*`) |
| `Access-Control-Allow-Methods`     | HTTP methods ที่อนุญาต (GET, POST, PUT, DELETE)               |
| `Access-Control-Allow-Headers`     | Headers ที่อนุญาตใน request (Content-Type, Authorization)     |
| `Access-Control-Max-Age`           | ระยะเวลาแคช preflight response (วินาที)                       |
| `Access-Control-Allow-Credentials` | อนุญาตให้ส่ง cookies                                          |

---

## 4. Preflight Request

การร้องขอ CORS บางประเภท จะทำ **preflight request** ก่อน (OPTIONS request)

```javascript
// ❌ สิ่งนี้จะทำ preflight request
fetch("https://api.example.com/users", {
  method: "POST", // POST ต้องการ preflight
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "John" }),
});
```

### ลำดับการทำงาน:

```
1. Browser ส่ง OPTIONS request ไป server
   OPTIONS /users
   Origin: https://myapp.com
   Access-Control-Request-Method: POST
   Access-Control-Request-Headers: Content-Type

2. Server ตอบกลับด้วย CORS headers
   Access-Control-Allow-Origin: https://myapp.com
   Access-Control-Allow-Methods: POST
   Access-Control-Allow-Headers: Content-Type

3. ถ้าตกลงกัน Browser ส่ง actual request (POST)
```

### ✅ Simple Request (ไม่ต้อง preflight)

```javascript
// GET, HEAD, POST เท่านั้น และ headers ต้องตรงตามกฎ
fetch("https://api.example.com/users", {
  method: "GET", // ไม่มี preflight
});
```

---

## 5. ตัวอย่างจริง: Client & Server CORS

### Server (Node.js + Express)

```javascript
const express = require("express");
const cors = require("cors");
const app = express();

app.use(express.json());

// อนุญาตเฉพาะ myapp.com
app.use(
  cors({
    origin: "https://myapp.com",
    credentials: true,
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
  }),
);

// Users data
const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

app.get("/api/users", (req, res) => {
  res.json(users);
});

app.post("/api/users", (req, res) => {
  const newUser = {
    id: users.length + 1,
    name: req.body.name,
  };
  users.push(newUser);
  res.json(newUser);
});

app.listen(5000, () => console.log("Server running on port 5000"));
```

### Client (HTML + JavaScript)

```html
<!DOCTYPE html>
<html lang="th">
  <head>
    <meta charset="UTF-8" />
    <title>CORS Example</title>
    <style>
      body {
        font-family: Arial;
        margin: 20px;
      }
      .user {
        border: 1px solid #ccc;
        padding: 10px;
        margin: 5px 0;
      }
    </style>
  </head>
  <body>
    <h1>ดึงข้อมูลผู้ใช้ (CORS)</h1>

    <button onclick="fetchUsers()">ดึงรายชื่อ</button>
    <button onclick="addUser()">เพิ่มผู้ใช้ใหม่</button>

    <div id="result"></div>

    <script>
      async function fetchUsers() {
        try {
          const response = await fetch("https://api.example.com/api/users");
          const users = await response.json();

          let html = "";
          users.forEach((user) => {
            html += `<div class="user">${user.id}. ${user.name}</div>`;
          });

          document.getElementById("result").innerHTML = html;
        } catch (error) {
          console.error("CORS Error:", error);
        }
      }

      async function addUser() {
        try {
          const response = await fetch("https://api.example.com/api/users", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ name: "Charlie" }),
          });

          const newUser = await response.json();
          console.log("Added:", newUser);

          fetchUsers(); // รีโหลดรายชื่อ
        } catch (error) {
          console.error("Error:", error);
        }
      }
    </script>
  </body>
</html>
```

---

## 6. วิธีแก้ปัญหา CORS

### วิธีที่ 1: ขอให้ Backend enable CORS ✅ (ที่ดีที่สุด)

หากคุณควบคุม backend เอง ให้เพิ่ม CORS middleware

---

### วิธีที่ 2: ใช้ CORS Proxy

```javascript
// ใช้บริการ proxy เช่น cors-anywhere.herokuapp.com
const corsProxy = "https://cors-anywhere.herokuapp.com/";
const apiUrl = "https://api.example.com/data";

fetch(corsProxy + apiUrl)
  .then((response) => response.json())
  .then((data) => console.log(data));
```

⚠️ **ข้อเสีย**: อาจช้า และขึ้นกับบริการ

---

### วิธีที่ 3: ใช้ JSONP (เก่าแล้ว)

```javascript
// <script src="https://api.example.com/data?callback=handleData"></script>

function handleData(data) {
  console.log(data);
}
```

⚠️ **ข้อเสีย**: ต้องให้ server สนับสนุน JSONP

---

### วิธีที่ 4: Backend ทำ Server-to-Server Request

Backend ของคุณทำ request ไปยัง external API แล้วส่งข้อมูลให้ Frontend

```javascript
// Frontend ส่อง Backend เรา (Same-Origin ✅)
fetch("https://myapp.com/api/external").then((response) => response.json());

// Backend route ของเรา
app.get("/api/external", async (req, res) => {
  // Backend ทำ request ไปยัง external API
  const externalResponse = await fetch("https://external-api.com/data");
  const data = await externalResponse.json();
  res.json(data); // ส่งข้อมูลกลับไป Frontend
});
```

✅ **ดีที่สุด**: รักษาความปลอดภัยและหลีกเลี่ยง CORS

---

## 7. Public APIs ที่รองรับ CORS

```javascript
// ✅ JSONPlaceholder (Mock data)
fetch("https://jsonplaceholder.typicode.com/posts/1");

// ✅ PokéAPI (Pokemon data)
fetch("https://pokeapi.co/api/v2/pokemon/1");

// ✅ OpenWeather API (ต้องมี API key)
fetch(
  "https://api.openweathermap.org/data/2.5/weather?q=Bangkok&appid=YOUR_KEY",
);

// ✅ GitHub API
fetch("https://api.github.com/users/octocat");

// ✅ Random User API
fetch("https://randomuser.me/api/");
```

---

## 8. Testing CORS ด้วย curl

```bash
# ตรวจสอบ CORS headers
curl -H "Origin: https://myapp.com" \
     -H "Access-Control-Request-Method: POST" \
     -H "Access-Control-Request-Headers: Content-Type" \
     -X OPTIONS \
     https://api.example.com/users \
     -v
```

---

## 9. Credentials (Cookies & Authentication)

เพื่อส่ง cookies หรือ authentication:

```javascript
// ❌ ไม่ส่ง cookies
fetch("https://api.example.com/user");

// ✅ ส่ง cookies
fetch("https://api.example.com/user", {
  credentials: "include", // ส่ง cookies
});

// Server ต้องตอบ:
// Access-Control-Allow-Credentials: true
// Access-Control-Allow-Origin: https://myapp.com (ระบุ origin, ไม่ใช่ *)
```

---

## 10. สรุป CORS Workflow

```
             ┌─────────────────────┐
             │  Frontend Browser   │
             │  https://myapp.com  │
             └──────────┬──────────┘
                        │
         ┌──────────────┼──────────────┐
         │                             │
         ▼                             ▼
   Same-Origin                    Cross-Origin
   https://myapp.com/api    https://api.example.com
        ✅ อนุญาต                  ❓ ต้องตรวจสอบ CORS

                            ┌─────────────────────┐
                            │ Backend ส่ง Headers  │
                            │ CORS: ?             │
                            └────────┬────────────┘
                                     │
                        ┌────────────┼────────────┐
                        │            │            │
                    ✅ อนุญาต    ❓ preflight  ❌ ปฏิเสธ
```

---

## สรุป

- **CORS** ป้องกัน malicious websites เข้าถึงข้อมูล
- **Same-Origin** = protocol + domain + port ต่างกัน
- Server ต้องส่ง **CORS headers** อนุญาต
- **Preflight request** (OPTIONS) ส่งก่อนสำหรับบาง requests
- **ดีที่สุด**: Backend route ทำ API request และส่งข้อมูลให้ Frontend
