คู่มือการไมเกรท SSO-Authen เข้ากับเว็บแอปพลิเคชัน (PHP, Node.js & WordPress)

อัปเดตล่าสุด: ธันวาคม 2025 Version: 3.3.0 สำนักวิทยบริการ มหาวิทยาลัยสงขลานครินทร์

คู่มือนี้อธิบายวิธีการรวม SSO-Authen ของสำนักวิทยบริการ มหาวิทยาลัยสงขลานครินทร์ ลงในแอปพลิเคชันที่มีอยู่แล้ว โดยแทนที่การยืนยันตัวตนด้วยชื่อผู้ใช้/รหัสผ่านแบบเดิม (AD/LDAP/SOAP/OAUTH)

SSO Authentication Flowchart
แผนภาพแสดงกระบวนการทำงานของระบบ SSO-Authen

Technology Stack

เลือกรูปแบบการเชื่อมต่อที่เหมาะสมกับระบบของคุณ:

Technology ข้อดี ข้อเสีย เหมาะสำหรับ
PHP • รองรับกว้าง
• Hosting ง่าย
• Community ใหญ่
• Performance ปานกลาง
• Real-time จำกัด
Traditional Web Apps
Node.js • Performance สูง
• Fullstack JS
• Real-time ดีเยี่ยม
• ต้องใช้ Node runtime
• ใช้ Memory สูงกว่า
Modern Web Apps, APIs, SPA
WordPress • ติดตั้งง่าย (Plugin)
• ไม่ต้องเขียนโค้ดเยอะ
• จัดการผ่าน UI
• ปรับแต่ง Logic ซับซ้อนได้ยากกว่า
• พึ่งพา WP Core
เว็บไซต์หน่วยงาน, Blog, CMS

Part 1: PHP Implementation

📦 ดาวน์โหลดชุดไมเกรท (PHP)

ดาวน์โหลดชุดไมเกรท SSO-Authen ที่ตรงกับเวอร์ชัน PHP ของคุณได้จากด้านล่าง

PHP 5.6

Firebase JWT v5

สำหรับเซิร์ฟเวอร์ที่ใช้ PHP 5.6.x รองรับ legacy systems

ไฟล์ที่รวมอยู่:

  • ✅ sso_callback.php (Enhanced)
  • ✅ sso_user_handler.php (Enhanced)
  • ✅ sso_config.php
  • ✅ composer.json (JWT v5)
ดาวน์โหลด PHP 5.6 Template

PHP 7.4+

Firebase JWT v6 (แนะนำ)

สำหรับเซิร์ฟเวอร์ที่ใช้ PHP 7.4+ และ PHP 8.x

ไฟล์ที่รวมอยู่:

  • ✅ sso_callback.php (Enhanced)
  • ✅ sso_user_handler.php (Enhanced)
  • ✅ sso_config.php
  • ✅ composer.json (JWT v6)
ดาวน์โหลด PHP 7.4+ Template

ขั้นตอนที่ 1: ลงทะเบียน Client

ให้ทำการลงทะเบียนแอปพลิเคชันของคุณเข้ากับระบบ SSO-Authen ที่ https://sso.oas.psu.ac.th

Redirect URI: https://<your-domain>/sso_callback.php
User Handler Endpoint: https://<your-domain>/sso_user_handler.php
สิ่งที่จะได้รับ
รายการ ตัวอย่าง การใช้งาน
Client ID client_xxx พารามิเตอร์ URL การเข้าสู่ระบบ
API Secret Key xxxxxxx... การยืนยัน API ของ User Handler *กรุณาเก็บไว้เป็นความลับ
JWT Secret Key xxxxxxx... การตรวจสอบ JWT ของ Callback *กรุณาเก็บไว้เป็นความลับ

ขั้นตอนที่ 2: สร้างไฟล์ SSO Config

สร้างไฟล์: includes/sso_config.php (ไฟล์นี้ใช้ได้กับทั้ง PHP 5.6 และ 7.4+)

<?php
/**
* SSO Configuration Template
* Prefer loading secrets from environment variables in production.
*/

// URL ของผู้ให้บริการ SSO
define('SSO_LOGIN_URL', 'https://sso.oas.psu.ac.th/public/login.php');
define('SSO_LOGOUT_URL', 'https://sso.oas.psu.ac.th/public/logout.php');

// Credential ที่ได้รับจากการลงทะเบียน client
define('SSO_CLIENT_ID', 'YOUR_CLIENT_ID_HERE');
define('SSO_API_SECRET', 'YOUR_API_SECRET_HERE');
define('SSO_JWT_SECRET', 'YOUR_JWT_SECRET_HERE');

// URL ของแอปพลิเคชัน
define('APP_BASE_URL', 'https://your-domain.example');
define('APP_CALLBACK_URL', APP_BASE_URL . '/sso_callback.php');
define('APP_USER_HANDLER_URL', APP_BASE_URL . '/sso_user_handler.php');
define('APP_HOME_URL', APP_BASE_URL . '/index.php');

// การกำหนดค่าฐานข้อมูล
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', 'password');
define('DB_NAME', 'dbname');

// การกำหนดค่า Role ประกอบด้วยชื่อคอลัมน์ในตารางผู้ใช้ และสิทธิ์เริ่มต้นสำหรับผู้ใช้ใหม่
define('SSO_ROLE_COLUMN', 'user_role');
define('SSO_DEFAULT_ROLE', 'user');

// หมายเหตุความปลอดภัย: ใน Production ให้ย้ายความลับไปยัง environment
// define('SSO_API_SECRET', getenv('SSO_API_SECRET'));
// define('SSO_JWT_SECRET', getenv('SSO_JWT_SECRET'));
?>

ขั้นตอนที่ 3: ติดตั้ง Composer

เลือกเวอร์ชัน composer.json ตาม PHP version ของ Server แล้วติดตั้งด้วยคำสั่ง composer install หรือใช้โฟลเดอร์ /vendor ที่เตรียมไว้ให้

สำหรับ PHP 5.6

{
  "require": {
    "firebase/php-jwt": "^5.2"
  },
  "config": {
    "platform": {
      "php": "5.6.40"
    }
  }
}

สำหรับ PHP 7.4+

{
  "require": {
    "firebase/php-jwt": "^6.0"
  },
  "config": {
    "platform": {
      "php": "7.4.33"
    }
  }
}

ขั้นตอนที่ 4a: สร้างไฟล์ User Handler Endpoint

สร้างไฟล์: sso_user_handler.php ที่ Root Directory

<?php
/**
* SSO User Handler - Template for PHP 7.4+
* - Enhanced with comprehensive error handling
*/

// Enable error logging
error_reporting(E_ALL);
ini_set('display_errors', 0);  
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/sso_error.log');

require_once __DIR__ . '/sso_config.php';
header('Content-Type: application/json; charset=utf-8'); 

try {
    // Verify API secret
    $api_secret = null;
    if (!empty($_SERVER['HTTP_X_API_SECRET'])) {
        $api_secret = $_SERVER['HTTP_X_API_SECRET'];
    } elseif (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
        if (preg_match('/Bearer\s+(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $m)) {
            $api_secret = $m[1];
        }
    }

    if ($api_secret !== SSO_API_SECRET) {
        error_log('SSO User Handler: Unauthorized access attempt');
        http_response_code(401);
        echo json_encode(['error' => 'Unauthorized']);
        exit;
    }

    $input = file_get_contents('php://input');
    $data = json_decode($input, true);
    $normalized = $data['normalizedUser'] ?? null;

    if (!$normalized) {
        error_log('SSO User Handler: Missing normalizedUser in request');
        http_response_code(400);
        echo json_encode(['error' => 'Missing normalizedUser']);
        exit;
    }

    $sso_id = $normalized['id'] ?? null;
    $email = $normalized['email'] ?? null;
    $name = $normalized['name'] ?? null;

    if (!$sso_id || !$email) {
        error_log('SSO User Handler: Missing id or email');
        http_response_code(400);
        echo json_encode(['error' => 'Missing id or email']);
        exit;
    }

    // Connect to DB with error handling
    $conn = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
    if (!$conn) {
        error_log('SSO User Handler: DB Connection Error - ' . mysqli_connect_error());
        http_response_code(500);
        echo json_encode(['error' => 'DB connection failed']);
        exit;
    }
    
    // Set charset to UTF-8
    mysqli_set_charset($conn, 'utf8mb4');

    $table = 'staff';

    // Find existing user
    $q = "SELECT id, staff_email, staff_name, sso_id, " . SSO_ROLE_COLUMN . " as user_role
          FROM $table WHERE staff_email = ? OR sso_id = ? LIMIT 1";
    
    $stmt = $conn->prepare($q);
    if (!$stmt) {
        error_log('SSO User Handler: Prepare Error - ' . $conn->error);
        http_response_code(500);
        echo json_encode(['error' => 'Database query preparation failed']);
        exit;
    }
    
    $stmt->bind_param('ss', $email, $sso_id);
    if (!$stmt->execute()) {
        error_log('SSO User Handler: Execute Error - ' . $stmt->error);
        http_response_code(500);
        echo json_encode(['error' => 'Database query execution failed']);
        exit;
    }
    
    $res = $stmt->get_result();
    $user_role = SSO_DEFAULT_ROLE;

    if ($res && $res->num_rows > 0) {
        $row = $res->fetch_assoc();
        $user_id = $row['id'];
        $user_role = $row['user_role'] ?? SSO_DEFAULT_ROLE;

        // Update record
        $uq = "UPDATE $table SET sso_id = ?, staff_name = ? WHERE id = ?";
        $ust = $conn->prepare($uq);
        if ($ust) {
            $ust->bind_param('ssi', $sso_id, $name, $user_id);
            $ust->execute();
        }
    } else {
        // Auto-create new user
        $iq = "INSERT INTO $table (sso_id, staff_email, staff_name, " . SSO_ROLE_COLUMN . ") VALUES (?, ?, ?, ?)";
        $ist = $conn->prepare($iq);
        if ($ist) {
            $ist->bind_param('ssss', $sso_id, $email, $name, SSO_DEFAULT_ROLE);
            if ($ist->execute()) {
                $user_id = $conn->insert_id;
                error_log('SSO User Handler: New user created - ID: ' . $user_id);
            } else {
                error_log('SSO User Handler: Insert Error - ' . $ist->error);
                http_response_code(500);
                echo json_encode(['error' => 'Failed to create new user']);
                exit;
            }
        }
    }

    $stmt->close();
    $conn->close();

    // Return user object with role information
    echo json_encode([
        'id' => (string)$user_id,
        'email' => $email,
        'name' => $name,
        'role' => $user_role
    ], JSON_UNESCAPED_UNICODE);  // Add JSON_UNESCAPED_UNICODE for Thai characters

} catch (Exception $e) {
    error_log('SSO User Handler Exception: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['error' => 'Internal server error', 'message' => $e->getMessage()]);
}
?>

ขั้นตอนที่ 4b: สร้างไฟล์ Callback Endpoint

สร้างไฟล์: sso_callback.php ที่ Root Directory

<?php
/**
* SSO Callback - PHP 7.4+ (firebase/php-jwt v6)
* - Enhanced with comprehensive error handling
*/

// Enable error logging
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/sso_error.log');

try {
    require_once __DIR__ . '/sso_config.php';
    session_start();

    $jwt = $_GET['token'] ?? null;
    if (!$jwt) {
        error_log('SSO Callback: No token provided');
        die('No token provided');
    }

    $payloadArr = null;
    if (file_exists(__DIR__ . '/vendor/autoload.php')) {
        require_once __DIR__ . '/vendor/autoload.php';
        try {
            $decoded = \Firebase\JWT\JWT::decode($jwt, new \Firebase\JWT\Key(SSO_JWT_SECRET, 'HS256'));
            $payloadArr = (array)$decoded;
        } catch (Exception $e) {
            error_log('SSO Callback JWT Error: ' . $e->getMessage());
            die('Invalid token: ' . $e->getMessage());
        }
    } else {
        // Basic decode without signature validation (Dev only!)
        $parts = explode('.', $jwt);
        if (count($parts) !== 3) {
            error_log('SSO Callback: Invalid JWT format');
            die('Invalid JWT format');
        }
        $payloadArr = json_decode(base64_decode(str_replace(array('-', '_'), array('+', '/'), $parts[1])), true);
        if (!$payloadArr) {
            error_log('SSO Callback: Invalid JWT payload');
            die('Invalid JWT payload');
        }
    }

    // Extract user data
    $data = $payloadArr['data'] ?? null;
    
    // CRITICAL: Convert object to array to prevent "Cannot use object as array" error
    if (is_object($data)) {
        $data = (array)$data;
    }
    
    if (!$data || !is_array($data)) {
        error_log('SSO Callback: Invalid data object');
        die('Invalid JWT payload (missing or invalid data object)');
    }

    $user_id = $data['id'] ?? null;
    $username = $data['email'] ?? null;
    $display = $data['name'] ?? null;
    $user_role = $data['role'] ?? 'user';  // Fallback to 'user' if role not provided

    if ($user_id === null || $username === null || $user_id === '' || $username === '') {
        error_log('SSO Callback: Missing user data - ' . print_r($data, true));
        die('Invalid JWT payload (missing user id or email in data)');
    }

    // Set session keys compatible with legacy app
    $_SESSION['LOGIN'] = 1;
    $_SESSION['USERNAME'] = $username;
    $_SESSION['USER_ID'] = $user_id;
    $_SESSION['USER_NAME'] = $display;
    $_SESSION['USER_ROLE'] = $user_role;
    $_SESSION['SSO_JWT'] = $jwt;

    // Log successful login for debugging
    error_log('SSO Callback Success: User ' . $user_id . ' logged in');

    $redirect = $_SESSION['original_url'] ?? 'manage/index.php?Login=True';
    unset($_SESSION['original_url']);
    header('Location: ' . $redirect);
    exit;
    
} catch (Exception $e) {
    error_log('SSO Callback Exception: ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine());
    die('Error processing SSO login: ' . $e->getMessage());
}
?>

ขั้นตอนที่ 5: อัปเดต UI

สร้างปุ่มเข้าสู่ระบบ (header.php)

<?php
require_once 'includes/sso_config.php';

// ... โค้ดเดิมที่มีอยู่ ...

if (isset($_SESSION['LOGIN']) && $_SESSION['LOGIN'] == 1) {
    // ผู้ใช้ได้เข้าสู่ระบบแล้ว
    echo '<div class="user-menu">';
    echo 'ยินดีต้อนรับ ' . htmlspecialchars($_SESSION['USERNAME']);

    // แสดงเมนูตามสิทธิ์
    $user_role = isset($_SESSION['USER_ROLE']) ? $_SESSION['USER_ROLE'] : 'user';
    
    if ($user_role === 'admin') {
        echo ' | <a href="/admin/users.php">จัดการผู้ใช้</a>';
    } elseif ($user_role === 'manager') {
        echo ' | <a href="/manager/reports.php">รายงาน</a>';
    }

    echo ' | <a href="logout.php">ออกจากระบบ</a>';
    echo '</div>';
} else {
    // ผู้ใช้ยังไม่ได้เข้าสู่ระบบ - แสดงปุ่ม SSO login
    $login_url = SSO_LOGIN_URL . '?client_id=' . urlencode(SSO_CLIENT_ID)
              . '&redirect_uri=' . urlencode(APP_CALLBACK_URL);

    echo '<div class="login-section">';
    echo '<a href="' . $login_url . '" class="btn btn-primary">เข้าสู่ระบบด้วย PSU SSO</a>';
    echo '</div>';
}
?>

ปรับปรุงลิงค์การออกจากระบบ (logout.php)

<?php
require_once 'includes/sso_config.php';
session_start();
session_destroy();

// Redirect ไปหน้า Logout ของ SSO
$logout_url = SSO_LOGOUT_URL . '?post_logout_redirect_uri='
            . urlencode('https://' . $_SERVER['HTTP_HOST'] . '/index.php');

header('Location: ' . $logout_url);
exit;
?>

ขั้นตอนที่ 6: ปรับปรุงฐานข้อมูล (ตารางผู้ใช้) ให้สอดคล้องกับระบบ

-- เพิ่มคอลัมน์สำหรับ SSO และ Role เข้าไปในตาราง staff (หรือตารางผู้ใช้เดิมของคุณ)
ALTER TABLE staff
  ADD COLUMN sso_id VARCHAR(100) UNIQUE,
  ADD COLUMN staff_email VARCHAR(100),
  ADD COLUMN user_role VARCHAR(50) DEFAULT 'user',
  MODIFY staff_name VARCHAR(255);

ขั้นตอนที่ 7: การทดสอบ (Testing)

ทดสอบยิง Request ไปยัง User Handler (จำลองการเรียกจาก SSO Server)

curl -X POST https://your-domain/sso_user_handler.php \
  -H "X-API-SECRET: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "normalizedUser": {
      "id": "12345",
      "email": "test@psu.ac.th",
      "name": "Test User"
    }
  }'
ผลลัพธ์ที่ควรได้: ต้องได้รับ JSON ที่มีฟิลด์ role ตอบกลับมาด้วย
{
  "id": "1",
  "email": "test@psu.ac.th",
  "name": "Test User",
  "role": "user"  <-- ตรวจสอบว่ามีบรรทัดนี้เพื่อยืนยันสิทธิ์
}

🛠️ การแก้ไขปัญหา

ปัญหา สาเหตุ วิธีแก้
"Did not receive user data" User Handler ไม่ได้รับเรียกหรือรูปแบบการตอบสนองผิด ตรวจสอบ API secret ตรงกัน ตรวจสอบให้แน่ใจว่าการตอบสนองมีฟิลด์ id
"Invalid token" ที่ callback ความไม่ตรงกันของลายเซ็น JWT ตรวจสอบว่า JWT_SECRET ตรงกับคีย์ของผู้ให้บริการ SSO
Session ไม่สร้างหลังการเข้าสู่ระบบ Callback ไม่ตั้งคีย์ session ตรวจสอบคีย์ session: $_SESSION['LOGIN'] และ $_SESSION['USERNAME']
User Handler ส่งกลับ 401 ไม่ได้ส่ง API secret หรือผิด ตรวจสอบว่า X-API-SECRET header ตรงกับ SSO_API_SECRET
ออกจากระบบไม่เปลี่ยนเส้นทางไปยัง SSO การกำหนดค่า LOGOUT_URL ผิด ตรวจสอบ URL ตรงกับ endpoint การออกจากระบบของผู้ให้บริการ SSO
Role ไม่แสดงใน UI Session ไม่มี $_SESSION['USER_ROLE'] ตรวจสอบว่า callback ตั้งค่า role ถูกต้อง และ JWT มีข้อมูล role
ผู้ใช้ใหม่ได้สิทธิ์ไม่ถูกต้อง ตาราง roles หรือ default role ไม่ถูกต้อง ตรวจสอบว่ามี role 'user' ในตาราง roles และ user handler กำหนดสิทธิ์ถูกต้อง
HTTP 500 แบบไม่มี error message ไม่มี error logging หรือ try-catch ตรวจสอบไฟล์ sso_error.log และดูว่ามี error อะไรบันทึก
Fatal error: Cannot use object as array ไม่แปลง object เป็น array เพิ่ม if (is_object($data)) { $data = (array)$data; } ใน callback

📝 หมายเหตุสำคัญ

การปรับปรุงเทมเพลต (Enhanced Template)

เทมเพลตที่แสดงในคู่มือนี้ได้รับการปรับปรุงตามประสบการณ์จากการใช้งานจริงในหลายโปรเจกส์ เพื่อป้องกันปัญหา HTTP 500 และเพิ่มความ robust

การปรับปรุงหลัก 7 ข้อ:

1. Error Logging - บันทึก error ลง sso_error.log
2. try-catch Wrapper - จับ exception ทุกประเภท
3. Object→Array Conversion - ป้องกัน "Cannot use object as array" error
4. Fallback Values - มี default values สำหรับ role
5. UTF-8 Support - รองรับภาษาไทย
6. Database Validation - ตรวจสอบ prepare() และ execute()
7. Comprehensive Logging - บันทึกทุกจุดสำคัญ

ประโยชน์ที่ได้รับ:

  • ✅ ป้องกัน HTTP 500 แบบไม่มี error message
  • ✅ Debug ได้ง่ายด้วย error log
  • ✅ รองรับภาษาไทยและ Unicode
  • ✅ ไม่มี silent failure
  • ✅ มี audit trail สำหรับ login/unauthorized access

วิธีตรวจสอบ Error Log

# ดู error log แบบ real-time
tail -f sso_error.log

# หรือใน Windows PowerShell
Get-Content sso_error.log -Wait -Tail 20
ตัวอย่าง log ที่คาดหวัง:
SSO Callback Success: User 123 logged in
SSO User Handler: New user created - ID: 456
ตัวอย่าง error:
SSO Callback: No token provided
SSO Callback JWT Error: Expired token
SSO User Handler: Unauthorized access attempt
SSO User Handler: DB Connection Error

Part 2: Node.js Implementation

📦 ดาวน์โหลดชุดไมเกรท (Node.js)

ชุดไมเกรทสำหรับ Node.js แบบ Stateful (Express Session)

ดาวน์โหลด Node.js Template

ขั้นตอนที่ 1: เตรียมโปรเจกต์

สร้างโฟลเดอร์โปรเจกต์และติดตั้ง Dependencies ที่จำเป็น

สำหรับ Web App ทั่วไปที่ต้องการ Session Management

npm init -y
npm install express express-session jsonwebtoken axios dotenv body-parser mysql2

ขั้นตอนที่ 2: ตัวแปรสภาพแวดล้อม (Environment Variables)

สร้างไฟล์ .env ที่ Root Directory

# .env
PORT=3000
NODE_ENV=development

# SSO Configuration
SSO_LOGIN_URL=https://sso.oas.psu.ac.th/public/login.php
SSO_LOGOUT_URL=https://sso.oas.psu.ac.th/public/logout.php
SSO_CLIENT_ID=YOUR_CLIENT_ID
SSO_API_SECRET=YOUR_API_SECRET
SSO_JWT_SECRET=YOUR_JWT_SECRET

# App URLs
APP_BASE_URL=http://localhost:3000
APP_CALLBACK_URL=/sso_callback
APP_USER_HANDLER_URL=/sso_user_handler

# Database
DB_HOST=localhost
DB_USER=root
DB_PASS=password
DB_NAME=psu_sso_db

# Session (Stateful Only)
SESSION_SECRET=your-secret-key

ขั้นตอนที่ 3: สร้าง Server

ตัวอย่างโค้ด server.js สำหรับจัดการ Authentication

require("dotenv").config();
const express = require("express");
const session = require("express-session");
const jwt = require("jsonwebtoken");
const mysql = require("mysql2/promise");
const app = express();

// 1. Setup Session
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true }
}));

// 2. Database Pool
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME
});

// 3. User Handler (Called by SSO Server)
app.post(process.env.APP_USER_HANDLER_URL, express.json(), async (req, res) => {
const apiSecret = req.headers["x-api-secret"];
if (apiSecret !== process.env.SSO_API_SECRET) {
    return res.status(401).json({ error: "Unauthorized" });
}

const { normalizedUser } = req.body;
// ... Logic to Insert/Update User in DB ...
// See full code in NodeJS.zip

res.json({ id: "1", role: "user" }); // Mock response
});

// 4. Callback (Called by Browser after Login)
app.get(process.env.APP_CALLBACK_URL, (req, res) => {
const token = req.query.token;
try {
    const decoded = jwt.verify(token, process.env.SSO_JWT_SECRET);
    // Save User to Session
    req.session.user = decoded.data;
    res.redirect("/");
} catch (err) {
    res.status(400).send("Invalid Token");
}
});

app.get("/", (req, res) => {
if (req.session.user) {
    // Escape HTML tags
    res.send(`Hello ${req.session.user.name} <a href="/logout">Logout</a>`);
} else {
    // Generate Login Link
    const loginUrl = `${process.env.SSO_LOGIN_URL}?client_id=${process.env.SSO_CLIENT_ID}&redirect_uri=${process.env.APP_BASE_URL}${process.env.APP_CALLBACK_URL}`;
    // Escape HTML tags
    res.send(`<a href="${loginUrl}">Login with PSU SSO</a>`);
}
});

app.listen(3000, () => console.log("Server running on port 3000"));

ขั้นตอนที่ 4: สั่งรัน Gateway (CLI)

คำสั่ง Command Line สำหรับรันเซิร์ฟเวอร์ในโหมดต่างๆ

สำหรับการทดสอบและพัฒนา (Development)

# ติดตั้ง dependencies (ถ้ายังไม่ได้ทำ)
npm install

# รันด้วย node ปกติ
node server.js

# หรือถ้าใช้ nodemon (แนะนำสำหรับ dev)
npx nodemon server.js

Part 3: WordPress Implementation

📦 ดาวน์โหลด Plugin (WordPress)

PSU SSO-Authen Plugin

  • ✅ WordPress 5.0 ขึ้นไป (แนะนำ)
  • ✅ PHP 7.4 หรือใหม่กว่า (รองรับ PHP 8)
  • ✅ เว็บไซต์ต้องใช้งานผ่าน HTTPS เท่านั้น

ดาวน์โหลด Plugin

ขั้นตอนที่ 1: ติดตั้ง Plugin

วิธีติดตั้งผ่านหน้า Admin

  1. เข้าสู่ระบบ WordPress Admin
  2. ไปที่เมนู Plugins > Add New (ปลั๊กอิน > เพิ่มปลั๊กอินใหม่)
  3. คลิกปุ่ม Upload Plugin (อัปโหลดปลั๊กอิน) ด้านบน
  4. เลือกไฟล์ psu-sso-authen.zip และกด Install Now (ติดตั้งตอนนี้)
  5. กด Activate (ใช้งาน) ปลั๊กอิน

ขั้นตอนที่ 2: ลงทะเบียนและตั้งค่า Config

ลงทะเบียนเว็บไซต์ของคุณที่ https://sso.oas.psu.ac.th โดยใช้ URL ดังนี้:

Redirect URI: https://your-domain.com/?psu_sso_action=callback
User Handler: https://your-domain.com/?psu_sso_action=user_handler

จากนั้นเพิ่มค่าคงที่ (Constants) ลงในไฟล์ wp-config.php (วางไว้ก่อนบรรทัด /* That's all, stop editing! */)

/** PSU SSO Gateway Configuration */
define('PSU_SSO_CLIENT_ID', 'ระบุ_Client_ID_ของคุณที่นี่');
define('PSU_SSO_API_SECRET', 'ระบุ_API_Secret_Key_ของคุณที่นี่');
define('PSU_SSO_JWT_SECRET', 'ระบุ_JWT_Secret_Key_ของคุณที่นี่');

// Optional Settings
// define('PSU_SSO_DISABLE_REGISTRATION', true); // ปิดรับสมาชิกใหม่
// define('PSU_SSO_HIDE_DEFAULT_FORM', true); // ซ่อนฟอร์ม Login เดิม

ขั้นตอนที่ 3: การจัดการสิทธิ์ (Roles)

👤 สิทธิ์เริ่มต้น

ผู้ใช้ใหม่ที่ Login ครั้งแรกจะได้รับสิทธิ์เป็น Subscriber โดยอัตโนมัติ

Admin สามารถปรับสิทธิ์ผู้ใช้ได้ที่เมนู Users

🔧 Custom Redirect

ใช้ Filter Hook เพื่อกำหนดหน้าปลายทางหลัง Login แยกตาม Role

add_filter('psu_sso_login_redirect_url', 
function($url, $user) {
    if (in_array('shop_manager', $user->roles)) {
        return home_url('/shop/');
    }
    return $url;
}, 10, 2);

🛠️ การแก้ไขปัญหา (WordPress)

ปัญหา วิธีแก้ไข
Missing Configuration ตรวจสอบไฟล์ wp-config.php ว่าใส่ค่าคงที่ครบ 3 ตัวหรือไม่
Invalid Token / User not found 1. ตรวจสอบ JWT Secret
2. ตรวจสอบ URL User Handler ในระบบ SSO
3. เปิดไฟล์ debug.log ในโฟลเดอร์ปลั๊กอิน
Login แล้วไม่ไปหน้า Admin ปลั๊กอินจะ Redirect เฉพาะ Administrator ไปหน้า Dashboard ส่วน Role อื่นจะไปหน้าแรก (แก้ไขได้ด้วย Hook)

🖧 โครงสร้างไฟล์หลังการติดตั้ง

PHP Project

project-root/
├── sso_callback.php
├── sso_user_handler.php
├── composer.json
├── vendor/
├── includes/
│   ├── sso_config.php
│   ├── header.php
│   └── auth_functions.php
└── admin/
    └── logout.php

Node.js Project

nodejs-app/
├── server.js
├── package.json
├── .env
├── database.sql
└── [logs and other files]

WordPress

wp-content/plugins/
└── psu-sso-authen/
    ├── psu-sso-authen.php
    ├── psu-sso-authen.js
    ├── readme.md
    └── ...