Ở Bài 41, chúng ta đã xây dựng REST API.
Ở Bài 42, chúng ta biết cách dùng API Resource để định dạng JSON.
Ở Bài 43, chúng ta đã học Laravel Sanctum và API Token.
Bây giờ chúng ta sẽ ghép tất cả lại để xây dựng một hệ thống:
Client
│
│ email + password
▼
POST /api/login
│
▼
Laravel kiểm tra tài khoản
│
├── Sai → 422 / 401
│
└── Đúng
│
▼
Sanctum Token
│
▼
Authorization: Bearer TOKEN
│
▼
Protected API
Đây chính là nền tảng authentication thường gặp khi Laravel đóng vai trò API Backend cho:
Website JavaScript
React
Vue
Next.js
Mobile App
Desktop App
ứng dụng bên thứ ba
Laravel 13 có thể sử dụng Sanctum để xác thực API bằng token. Token được gửi trong HTTP header dưới dạng Bearer Token.
1. API Authentication là gì?
Authentication nghĩa là:
Xác định người đang gọi API là ai.
Ví dụ:
POST /api/login
Client gửi:
{
"email": "admin@example.com",
"password": "12345678"
}
Laravel kiểm tra database.
Nếu chính xác:
{
"message": "Đăng nhập thành công",
"token": "1|xxxxxxxxxxxxxxxx"
}
Client giữ token.
Những request sau đó sẽ gửi:
Authorization: Bearer 1|xxxxxxxxxxxxxxxx
Laravel sẽ biết:
Request này
↓
có token
↓
token thuộc User nào?
↓
User #5
↓
authenticated
2. Authentication khác Authorization
Hai khái niệm này rất dễ nhầm.
Authentication
Xác định:
Người này là ai?
Ví dụ:
User #5
email: admin@example.com
Authorization
Xác định:
Người này có được phép làm việc đó không?
Ví dụ:
User #5
role = admin
được phép:
DELETE /api/posts/10
Nhưng:
User #8
role = user
không được phép.
Có thể hình dung:
Authentication
↓
"Bạn là ai?"
↓
User #5
↓
Authorization
↓
"Bạn được làm gì?"
↓
Policy / Gate / Role
Trong bài này tập trung vào Authentication.
Authorization sẽ kết hợp với Policy ở các phần nâng cao.
3. Chuẩn bị Sanctum
Nếu project Laravel 13 chưa cài API/Sanctum:
php artisan install:api
Sau đó migrate:
php artisan migrate
Lệnh install:api là cách Laravel hiện đại thiết lập API routing và Sanctum cho ứng dụng.
Trong project sẽ có:
routes/
web.php
api.php
Lưu ý:
Với Laravel 13, không nên mặc định cho rằng
routes/api.phpluôn tồn tại trong project mới. Nếu chưa có API routing, sử dụngphp artisan install:api.
4. User Model
Mở:
app/Models/User.php
Thêm:
use Laravel\Sanctum\HasApiTokens;
Sau đó sử dụng trait:
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
// ...
}
HasApiTokens cung cấp các chức năng cần thiết để User tạo và quản lý Sanctum token.
5. API Authentication Controller
Tạo controller:
php artisan make:controller Api/AuthController
File:
app/Http/Controllers/Api/AuthController.php
Chúng ta sẽ xây dựng:
register()
login()
user()
logout()
logoutAll()
6. Register API
Đầu tiên tạo chức năng đăng ký.
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'email',
'max:255',
'unique:users,email',
],
'password' => [
'required',
'string',
'min:8',
'confirmed',
],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken('blog-api')->plainTextToken;
return response()->json([
'message' => 'Đăng ký thành công',
'user' => $user,
'token' => $token,
], 201);
}
}
7. Tại sao phải Hash password?
Không được lưu:
'password' => $request->password
Ví dụ:
12345678
là cực kỳ nguy hiểm.
Thay vào đó:
Hash::make($validated['password'])
Database sẽ lưu password đã được hash.
Ví dụ:
$2y$12$................................
Khi login, Laravel sẽ kiểm tra password thông qua cơ chế hashing.
8. Login API
Bây giờ tạo login.
Thêm method:
public function login(Request $request)
{
$validated = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
]);
$user = User::where('email', $validated['email'])->first();
if (
!$user ||
!Hash::check(
$validated['password'],
$user->password
)
) {
throw ValidationException::withMessages([
'email' => ['Email hoặc mật khẩu không chính xác.'],
]);
}
$token = $user
->createToken('blog-api')
->plainTextToken;
return response()->json([
'message' => 'Đăng nhập thành công',
'user' => $user,
'token' => $token,
]);
}
Luồng hoạt động:
email
↓
tìm User
↓
Hash::check()
↓
password đúng?
│
├── NO → lỗi
│
└── YES
↓
createToken()
↓
plainTextToken
↓
JSON
Laravel/Sanctum cũng sử dụng mô hình kiểm tra email/password rồi tạo token cho các client như mobile app.
9. Token là gì?
Sau khi login thành công:
$token = $user
->createToken('blog-api')
->plainTextToken;
Chúng ta nhận được:
1|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Token này sẽ được client sử dụng cho những API yêu cầu authentication.
Ví dụ:
Authorization: Bearer 1|xxxxxxxxxxxxxxxx
10. Không lưu plain token trong database
Sanctum không lưu nguyên token dạng plain text trong database.
Token được hash trước khi lưu.
Giá trị plain-text chỉ được trả về thông qua:
$token->plainTextToken
sau khi token được tạo.
Vì vậy client phải lưu token ngay sau khi login thành công.
11. Route Login
Mở:
routes/api.php
Thêm:
use App\Http\Controllers\Api\AuthController;
Route::post('/register', [
AuthController::class,
'register'
]);
Route::post('/login', [
AuthController::class,
'login'
]);
Khi đó:
POST /api/register
POST /api/login
là API public.
12. Test Register
Dùng Postman.
Request:
POST
http://127.0.0.1:8000/api/register
Body:
{
"name": "Nguyen Van A",
"email": "a@example.com",
"password": "12345678",
"password_confirmation": "12345678"
}
Nếu thành công:
{
"message": "Đăng ký thành công",
"user": {
"id": 10,
"name": "Nguyen Van A",
"email": "a@example.com"
},
"token": "10|xxxxxxxxxxxxxxxx"
}
HTTP Status:
201 Created
13. Test Login
Request:
POST
http://127.0.0.1:8000/api/login
Body:
{
"email": "a@example.com",
"password": "12345678"
}
Kết quả:
{
"message": "Đăng nhập thành công",
"user": {
"id": 10,
"name": "Nguyen Van A",
"email": "a@example.com"
},
"token": "10|xxxxxxxxxxxxxxxx"
}
Copy:
10|xxxxxxxxxxxxxxxx
để test những API private.
14. Tạo API User
Bây giờ chúng ta cần API:
GET /api/user
API này trả về user đang đăng nhập.
Trong AuthController:
public function user(Request $request)
{
return response()->json([
'user' => $request->user(),
]);
}
Nhưng API này phải được bảo vệ.
15. Middleware auth:sanctum
Trong:
routes/api.php
viết:
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', [
AuthController::class,
'user'
]);
});
Bây giờ:
GET /api/user
không còn là API public.
Client phải gửi:
Authorization: Bearer TOKEN
Sanctum sẽ kiểm tra token và xác định User tương ứng.
16. Request có Token
Trong Postman:
GET
http://127.0.0.1:8000/api/user
Headers:
Accept: application/json
Authorization: Bearer 10|xxxxxxxxxxxxxxxx
Nếu token hợp lệ:
{
"user": {
"id": 10,
"name": "Nguyen Van A",
"email": "a@example.com"
}
}
17. Nếu không có Token?
Request:
GET /api/user
nhưng không có:
Authorization: Bearer ...
Laravel không xác định được người dùng.
API protected sẽ trả về lỗi authentication, thường là:
401 Unauthorized
Điều này có nghĩa:
Request chưa được xác thực.
18. 401 và 403 khác nhau
Đây là phần rất quan trọng khi làm API.
401 Unauthorized
Người dùng:
chưa đăng nhập
hoặc:
token không hợp lệ
Ví dụ:
GET /api/user
không có token.
403 Forbidden
Người dùng đã đăng nhập nhưng:
không có quyền thực hiện hành động
Ví dụ:
User
↓
đã authenticated
↓
DELETE /api/posts/10
↓
Policy từ chối
↓
403 Forbidden
Tóm lại:
401
↓
"Bạn là ai?"
403
↓
"Tôi biết bạn là ai,
nhưng bạn không được phép làm việc này."
19. Logout API
Login tạo token.
Logout phải thu hồi token.
Trong AuthController:
public function logout(Request $request)
{
$request
->user()
->currentAccessToken()
->delete();
return response()->json([
'message' => 'Đăng xuất thành công',
]);
}
Route:
Route::post('/logout', [
AuthController::class,
'logout'
]);
Đặt trong:
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', [
AuthController::class,
'user'
]);
Route::post('/logout', [
AuthController::class,
'logout'
]);
});
Sanctum hỗ trợ thu hồi token hiện tại thông qua currentAccessToken()->delete().
20. Logout tất cả thiết bị
Giả sử user đăng nhập:
Chrome
Mobile
Laptop
Tablet
mỗi thiết bị có thể có một token.
Database:
tokens
ID USER NAME
1 5 chrome
2 5 mobile
3 5 laptop
4 5 tablet
Nếu muốn đăng xuất tất cả:
public function logoutAll(Request $request)
{
$request
->user()
->tokens()
->delete();
return response()->json([
'message' => 'Đã đăng xuất khỏi tất cả thiết bị.',
]);
}
Route:
Route::post('/logout-all', [
AuthController::class,
'logoutAll'
]);
Sanctum cung cấp quan hệ tokens để quản lý các token của User.
21. Dùng API Resource
Ở Bài 42 chúng ta đã học:
PostResource
UserResource
CategoryResource
Không nên trả toàn bộ User model một cách tùy tiện.
Ví dụ tạo:
php artisan make:resource UserResource
File:
app/Http/Resources/UserResource.php
Code:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'role' => $this->role,
'created_at' => $this->created_at,
];
}
}
Sau đó:
use App\Http\Resources\UserResource;
Sửa:
public function user(Request $request)
{
return new UserResource(
$request->user()
);
}
Response sẽ được kiểm soát tốt hơn.
22. AuthController hoàn chỉnh
Sau khi ghép các phần lại:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$validated = $request->validate([
'name' => [
'required',
'string',
'max:255',
],
'email' => [
'required',
'email',
'max:255',
'unique:users,email',
],
'password' => [
'required',
'string',
'min:8',
'confirmed',
],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make(
$validated['password']
),
]);
$token = $user
->createToken('blog-api')
->plainTextToken;
return response()->json([
'message' => 'Đăng ký thành công',
'user' => new UserResource($user),
'token' => $token,
], 201);
}
public function login(Request $request)
{
$validated = $request->validate([
'email' => [
'required',
'email',
],
'password' => [
'required',
'string',
],
]);
$user = User::where(
'email',
$validated['email']
)->first();
if (
!$user ||
!Hash::check(
$validated['password'],
$user->password
)
) {
throw ValidationException::withMessages([
'email' => [
'Email hoặc mật khẩu không chính xác.'
],
]);
}
if (!$user->is_active) {
throw ValidationException::withMessages([
'email' => [
'Tài khoản đã bị khóa.'
],
]);
}
$token = $user
->createToken('blog-api')
->plainTextToken;
return response()->json([
'message' => 'Đăng nhập thành công',
'user' => new UserResource($user),
'token' => $token,
]);
}
public function user(Request $request)
{
return new UserResource(
$request->user()
);
}
public function logout(Request $request)
{
$request
->user()
->currentAccessToken()
->delete();
return response()->json([
'message' => 'Đăng xuất thành công.',
]);
}
public function logoutAll(Request $request)
{
$request
->user()
->tokens()
->delete();
return response()->json([
'message' => 'Đã đăng xuất khỏi tất cả thiết bị.',
]);
}
}
Ở đây có một điểm rất thực tế đối với Blog CMS của chúng ta:
if (!$user->is_active)
Nếu admin đã khóa tài khoản:
is_active = false
thì user đó không thể login API.
23. routes/api.php hoàn chỉnh
File:
routes/api.php
<?php
use App\Http\Controllers\Api\AuthController;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Public API
|--------------------------------------------------------------------------
*/
Route::post('/register', [
AuthController::class,
'register'
]);
Route::post('/login', [
AuthController::class,
'login'
]);
/*
|--------------------------------------------------------------------------
| Protected API
|--------------------------------------------------------------------------
*/
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', [
AuthController::class,
'user'
]);
Route::post('/logout', [
AuthController::class,
'logout'
]);
Route::post('/logout-all', [
AuthController::class,
'logoutAll'
]);
});
24. Danh sách API
Sau khi hoàn thành:
| Method | URL | Authentication |
|---|---|---|
| POST | /api/register | Không |
| POST | /api/login | Không |
| GET | /api/user | Có |
| POST | /api/logout | Có |
| POST | /api/logout-all | Có |
Luồng:
REGISTER
↓
LOGIN
↓
TOKEN
↓
Bearer Token
↓
GET /api/user
↓
POST /api/logout
25. Authentication cho Posts
Bây giờ chúng ta có thể kết hợp authentication với Blog CMS.
Ví dụ:
GET /api/posts
cho phép public.
Nhưng:
POST /api/posts
PUT /api/posts/{post}
DELETE /api/posts/{post}
yêu cầu login.
Có thể viết:
Route::get('/posts', [
PostController::class,
'index'
]);
Route::middleware('auth:sanctum')->group(function () {
Route::post('/posts', [
PostController::class,
'store'
]);
Route::put('/posts/{post}', [
PostController::class,
'update'
]);
Route::delete('/posts/{post}', [
PostController::class,
'destroy'
]);
});
Khi đó:
GET /api/posts
↓
PUBLIC
POST /api/posts
↓
AUTHENTICATION
↓
Sanctum
DELETE /api/posts/10
↓
AUTHENTICATION
↓
AUTHORIZATION
↓
Policy
Đây chính là kiến trúc API thực tế.
26. Authentication chưa phải Authorization
Đây là điểm cần nhớ.
Có token:
Bearer xxxxx
chỉ chứng minh:
User đã authenticated
Không có nghĩa:
User được phép làm mọi thứ.
Ví dụ:
User #10
role = user
có thể:
POST /api/posts
nhưng không nhất thiết được:
DELETE /api/users/5
Để kiểm tra quyền:
Authentication
↓
Sanctum
↓
User
↓
Authorization
↓
Policy / Gate / Role
Đây là lý do các bài:
Bài 43 — Sanctum
Bài 44 — API Authentication
sẽ tiếp tục kết nối rất tốt với kiến thức:
Bài 25 — Authorization
27. Token Ability
Sanctum còn hỗ trợ token abilities.
Ví dụ:
$token = $user->createToken(
'mobile',
['posts:read']
)->plainTextToken;
Token này có ability:
posts:read
Sau đó có thể kiểm tra:
$request->user()->tokenCan('posts:read')
Ví dụ:
if (!$request->user()->tokenCan('posts:read')) {
abort(403);
}
Sanctum cho phép gắn abilities/scopes vào token để giới hạn những hành động token có thể thực hiện.
28. Ví dụ thực tế
Giả sử có:
Website
Mobile App
Admin App
Một User có thể có:
Token 1 → website
Token 2 → mobile
Token 3 → admin
Mỗi token có thể có mục đích khác nhau.
Ví dụ:
mobile
↓
posts:read
admin
↓
posts:read
posts:create
posts:update
posts:delete
Như vậy hệ thống có thể kiểm soát API ở mức chi tiết hơn.
29. Test bằng Postman
Bước 1 — Register
POST /api/register
Body:
{
"name": "Admin",
"email": "admin@example.com",
"password": "12345678",
"password_confirmation": "12345678"
}
Bước 2 — Login
POST /api/login
Body:
{
"email": "admin@example.com",
"password": "12345678"
}
Copy:
token
Bước 3 — User
GET /api/user
Header:
Authorization: Bearer TOKEN
Bước 4 — Logout
POST /api/logout
Header:
Authorization: Bearer TOKEN
Sau khi logout:
TOKEN
↓
REVOKED
Token đó không còn dùng được nữa.
30. Một lỗi thường gặp
Sai:
Authorization: TOKEN
Đúng:
Authorization: Bearer TOKEN
Ví dụ:
Authorization: Bearer 1|abc123xyz
Từ khóa:
Bearer
rất quan trọng.
Sanctum API token được truyền qua Authorization header dưới dạng Bearer token.
31. Không đưa Token vào URL
Không nên:
/api/posts?token=xxxxx
hoặc:
/api/user/xxxxx
Thay vào đó:
Authorization: Bearer TOKEN
Token là thông tin xác thực, vì vậy phải hạn chế tối đa việc để nó xuất hiện trong URL, log hoặc nơi có thể bị lưu lại ngoài ý muốn.
32. Không commit Token vào Git
Không viết:
$token = '1|abcdef123456';
vào source code.
Không commit token thật vào:
GitHub
GitLab
Bitbucket
Token phải được quản lý như credential.
Nếu token bị lộ:
REVOKE
và tạo token mới.
33. Laravel Web Login và API Login
Trong Blog CMS hiện tại của chúng ta có:
Breeze
Breeze xử lý:
Web Login
Web Register
Session
Cookie
Trong khi API Authentication sử dụng:
Sanctum
Token
Bearer
Có thể hình dung:
Laravel 13
│
┌──────────┴──────────┐
│ │
WEB APP API
│ │
Breeze Sanctum
│ │
Session Token
│ │
Browser Mobile / SPA
Hai hệ thống có thể cùng tồn tại trong một project.
34. API Authentication hoàn chỉnh
Kiến trúc hiện tại:
Laravel 13 Blog CMS
│
┌──────────────┴──────────────┐
│ │
WEB API
│ │
Breeze Sanctum
│ │
Session/Cookie Token
│ │
Browser Mobile / SPA
│
▼
Authorization Header
│
Bearer Token
│
▼
auth:sanctum
│
▼
User
│
┌─────────┴─────────┐
│ │
API Resource Policy
│ │
▼ ▼
JSON Permission
Đây là mô hình rất gần với một Laravel API Backend thực tế.
35. Kiểm tra Route
Chạy:
php artisan route:list
Bạn sẽ thấy nhóm API tương tự:
POST api/register
POST api/login
GET api/user
POST api/logout
POST api/logout-all
Các route protected sẽ có:
auth:sanctum
36. Bài tập thực hành
Bài tập 1
Tạo:
POST /api/register
cho phép User đăng ký.
Bài tập 2
Tạo:
POST /api/login
và trả về:
{
"message": "...",
"user": {},
"token": "..."
}
Bài tập 3
Tạo:
GET /api/user
yêu cầu:
auth:sanctum
Bài tập 4
Tạo:
POST /api/logout
để thu hồi token hiện tại.
Bài tập 5
Tạo:
POST /api/logout-all
để thu hồi toàn bộ token của User.
Bài tập 6
Thử gọi:
GET /api/user
không có token.
Quan sát:
401
Sau đó đăng nhập và thử lại với:
Authorization: Bearer TOKEN
37. Ghi nhớ
Authentication
Xác định User là ai.
Authorization
Xác định User được làm gì.
Sanctum
API Token Authentication
Login
createToken()
Token
$token->plainTextToken
Protected API
auth:sanctum
Current User
$request->user()
Logout
$request
->user()
->currentAccessToken()
->delete();
Logout All
$request
->user()
->tokens()
->delete();
Bearer Token
Authorization: Bearer TOKEN
38. Tổng kết Bài 44
Sau bài này, chúng ta đã hoàn thành phần:
REST API
↓
API Resource
↓
Sanctum
↓
API Authentication
Và Blog CMS hiện tại đã có thể xây dựng luồng:
REGISTER
↓
LOGIN
↓
SANCTUM TOKEN
↓
BEARER TOKEN
↓
AUTHENTICATED USER
↓
PROTECTED API
↓
LOGOUT
↓
REVOKE TOKEN
Quan trọng hơn, chúng ta đã phân biệt được:
Authentication
≠
Authorization
Authentication trả lời:
Bạn là ai?
Authorization trả lời:
Bạn được phép làm gì?
Đây là hai khái niệm nền tảng khi xây dựng API thực tế.
🎯 Sau Bài 44
Phần REST API của khóa học đã hoàn thành:
Bài 41 — REST API
↓
Bài 42 — API Resource
↓
Bài 43 — Sanctum
↓
Bài 44 — API Authentication
Tiếp theo chúng ta chuyển sang:
🚀 PHẦN 10 — TRIỂN KHAI
Bài 45 — Deploy Shared Hosting
Chúng ta sẽ đưa Blog CMS từ:
localhost
lên:
Shared Hosting
và bắt đầu học những vấn đề rất thực tế như:
Upload source
Database
.env
APP_KEY
Storage
public/
Document Root
Migration
Vite build
Permission
Đây là bước chuyển từ:
"Laravel chạy được trên máy mình"
sang:
"Laravel chạy được trên server thật."




Không có nhận xét nào:
Đăng nhận xét