[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 2: Đăng nhập & Đăng xuất
Chào các bạn, mình đã quay trở lại!
Sau khi người dùng đã có tài khoản từ Phần 1, bước tiếp theo chắc chắn là phải cho họ "vào nhà". Trong môi trường API, việc xác thực (Authentication) có chút khác biệt so với Web truyền thống (dùng Session/Cookie). Tuy nhiên, trước khi tiến tới những khái niệm phức tạp như JWT, chúng ta cần nắm vững cách kiểm tra thông tin và quản lý luồng dữ liệu.
1. Tầng Model: Kiểm tra thông tin người dùng
Tại file User.php, chúng ta cần một phương thức để tìm người dùng theo email và quan trọng nhất là phải kiểm tra xem mật khẩu họ nhập vào có khớp với mật khẩu đã được mã hóa trong Database hay không.
File: app/Models/User.php (Cập nhật thêm method)
<?php
namespace App\Models;
use App\Core\Database;
use PDO;
class User {
protected $db;
public function __construct() {
$this->db = Database::getInstance();
}
// ... (Giữ nguyên các hàm cũ từ Phần 1)
/**
* Logic kiểm tra đăng nhập
*/
public function attemptLogin($email, $password)
{
// 1. Tìm user theo email
$stmt = $this->db->prepare("SELECT * FROM Users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
// 2. Sử dụng password_verify để so khớp mật khẩu đã hash
if ($user && password_verify($password, $user['password'])) {
return $user;
}
return false;
}
}
Note từ Blogger: Tuyệt đối không bao giờ dùng MD5 hay lưu mật khẩu dạng plain-text nhé các bạn. Hàm password_verify là "vũ khí" tiêu chuẩn của PHP để chống lại các cuộc tấn công brute-force mật khẩu.
2. Tầng Controller: Điều phối Đăng nhập/Đăng xuất
Bây giờ, chúng ta sẽ mở rộng AuthController để tiếp nhận request từ Client, gọi Model xử lý và trả về phản hồi kèm theo một "Token giả" để mô phỏng cơ chế xác thực.
File: app/Controllers/AuthController.php (Cập nhật)
<?php
namespace App\Controllers;
use App\Models\User;
use App\Core\Response;
class AuthController {
// ... (Hàm register cũ)
public function login() {
$input = json_decode(file_get_contents("php://input"), true);
// Validation đầu vào
if (empty($input['email']) || empty($input['password'])) {
Response::json(['error' => 'Email and password are required'], 422);
}
$userModel = new User();
$user = $userModel->attemptLogin($input['email'], $input['password']);
if (!$user) {
Response::json(['error' => 'Invalid credentials (Email hoặc mật khẩu không đúng)'], 401);
}
/**
* Giả lập token (Mock Token)
* Trong thực tế, đây là nơi bạn sẽ generate JWT (Json Web Token)
*/
$fakeToken = bin2hex(random_bytes(32));
Response::json([
'message' => 'Login successful',
'user' => [
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
],
'access_token' => $fakeToken
]);
}
public function logout() {
/**
* Đối với REST API (Stateless), server thường không lưu session.
* Logout ở phía Client chỉ đơn giản là xóa Token.
* Ở phía Server, nếu dùng JWT bạn có thể đưa token vào Blacklist trong Redis.
*/
Response::json(['message' => 'Logout successful']);
}
}
3. Cấu hình Route mới
Đừng quên đăng ký các "ngõ" mới này vào file index.php để Router biết đường mà chạy nhé.
File: public/index.php
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use App\Controllers\AuthController;
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
$controller = new AuthController();
// Simple Router Pattern
if ($uri === '/api/register' && $method === 'POST') {
$controller->register();
} elseif ($uri === '/api/login' && $method === 'POST') {
$controller->login();
} elseif ($uri === '/api/logout' && $method === 'POST') {
$controller->logout();
} else {
http_response_code(404);
Response::json(['error' => 'Route not found'], 404);
}
4. Kiểm thử tính năng (Test with Curl)
Hãy đảm bảo bạn đã chạy server (ví dụ: php -S localhost:8000 -t public).
Thử Đăng nhập:
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-d '{"email":"nguyenb@gmail.com","password":"password123"}'
Thử Đăng xuất:
curl -X POST http://localhost:8000/api/logout
Điểm lại các lưu ý bảo mật
| Hạng mục | Trạng thái | Giải thích |
|---|---|---|
| Hash Mật khẩu | ✅ | Luôn dùng password_hash và password_verify. |
| Mô phỏng Token | ⚠️ | Hiện tại là random string, phần sau chúng ta sẽ nâng cấp lên JWT thực thụ. |
| HTTP Method | ✅ | Dùng POST để gửi dữ liệu nhạy cảm (mật khẩu) thay vì GET. |
| Validation | ✅ | Kiểm tra rỗng và định dạng dữ liệu ngay tại Controller. |
Lời kết & Gợi ý cho phần 3
Chúng ta đã hoàn thành luồng cơ bản của Authentication. Tuy nhiên, một hệ thống thực tế không thể dừng lại ở "token giả". Ở phần tiếp theo, mình sẽ hướng dẫn các bạn:
Cài đặt thư viện Firebase JWT để tạo Token thật.
Xây dựng AuthMiddleware để bảo vệ các route "nhạy cảm" (chỉ những ai đã đăng nhập mới được truy cập).
Nếu bạn có thắc mắc gì về logic xử lý mật khẩu hay cách tổ chức Controller, hãy để lại bình luận bên dưới nhé! Đừng quên Upvote để tiếp thêm động lực cho mình ra phần tiếp theo. Hẹn gặp lại các bạn!
All Rights Reserved