NTM Solutions

Thứ Bảy, 19 tháng 9, 2026

🔐LARAVEL 13 — BÀI 43: SANCTUM

Laravel Sanctum cung cấp cơ chế xác thực nhẹ cho API token, SPA và các ứng dụng mobile.

Bài 41, chúng ta đã xây dựng REST API.

Bài 42, chúng ta dùng API Resource để kiểm soát dữ liệu JSON.

Nhưng API hiện tại có một vấn đề:

GET     /api/posts
POST    /api/posts
PUT     /api/posts/1
DELETE  /api/posts/1

Ai cũng có thể gửi request.

Ví dụ:

Anonymous User
       ↓
POST /api/posts
       ↓
Tạo bài viết?

Đây rõ ràng không phải điều chúng ta muốn.

Chúng ta cần biết:

Ai đang gọi API?

Và:

Người đó có được phép gọi API này không?

Đó là lúc Laravel Sanctum xuất hiện.


1. Laravel Sanctum là gì?

Sanctum là cơ chế xác thực nhẹ được Laravel cung cấp cho:

  • API token

  • SPA

  • Mobile application

  • Các API đơn giản cần token authentication

Sanctum cho phép user tạo nhiều personal access token và dùng token đó để xác thực các request API.

Có thể hình dung:

Client
   │
   │ Authorization: Bearer TOKEN
   ↓
Laravel API
   │
   ↓
Sanctum
   │
   ├── Token hợp lệ?
   │
   └── User nào?
   ↓
Controller

Nếu token hợp lệ:

Request
   ↓
Authenticated User
   ↓
Controller

Nếu không hợp lệ:

Request
   ↓
401 Unauthorized

2. Sanctum không phải là Authorization

Đây là điểm rất quan trọng.

Authentication:

Bạn là ai?

Authorization:

Bạn có quyền làm gì?

Ví dụ:

Authentication
    ↓
User ID = 10

Sau đó:

Authorization
    ↓
User 10 có được xóa Post 5 không?

Sanctum chủ yếu giải quyết vấn đề:

Authentication

Còn:

Authorization
Policies
Gates
Roles
Permissions

là những phần khác của Laravel.


3. Sanctum hoạt động như thế nào?

Ví dụ user đăng nhập và nhận token:

User
 ↓
Login
 ↓
Sanctum
 ↓
API Token

Ví dụ token:

1|abc123xyz...

Client lưu token.

Sau đó gửi:

Authorization: Bearer 1|abc123xyz...

Laravel nhận request:

Request
   ↓
Sanctum
   ↓
Kiểm tra token
   ↓
User

Nếu hợp lệ:

$request->user()

sẽ trả về user đang đăng nhập.


4. Cài Sanctum trong Laravel 13

Với Laravel hiện đại, cách đơn giản để cài API routing và Sanctum là:

php artisan install:api

Lệnh này được Laravel sử dụng để thiết lập API routing và Sanctum.

Chạy:

php artisan install:api

Sau đó chạy migration:

php artisan migrate

5. Kiểm tra project

Sau khi cài đặt, kiểm tra:

routes/
├── web.php
├── api.php
└── console.php

API route:

routes/api.php

Sanctum sử dụng database để quản lý personal access tokens.

Trong database sẽ có bảng:

personal_access_tokens

Bảng này được Sanctum dùng để lưu thông tin token đã được cấp.


6. User Model cần HasApiTokens

Mở:

app/Models/User.php

Thêm:

use Laravel\Sanctum\HasApiTokens;

Sau đó:

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    // ...
}

Trong project thực tế, User Model của chúng ta có thể đang sử dụng nhiều trait khác nhau.

Chỉ cần bổ sung:

HasApiTokens

vào danh sách trait.

Sanctum cung cấp trait này để User có thể tạo và quản lý API tokens.


7. Tạo API Token

Sanctum cung cấp:

createToken()

Ví dụ:

$token = $user->createToken('blog-api');

Token được tạo ra có thể lấy bằng:

$token->plainTextToken

Ví dụ:

return [
    'token' => $token->plainTextToken
];

Sanctum chỉ cung cấp giá trị plaintext của token ngay lúc token được tạo; token được lưu dưới dạng hash trong database. Vì vậy giá trị plaintext phải được bảo vệ và gửi cho client tại thời điểm cấp token.


8. Tạo API Token bằng Route

Để học Sanctum, chúng ta có thể tạo một route demo.

routes/api.php:

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('/tokens/create', function (Request $request) {

    $token = $request->user()->createToken(
        $request->token_name
    );

    return [
        'token' => $token->plainTextToken
    ];
});

Nhưng có một vấn đề:

$request->user()

cần có authenticated user.

Do đó trong project thật, chúng ta cần thiết kế endpoint cấp token dựa trên thông tin đăng nhập của user.

Phần login API hoàn chỉnh sẽ được xử lý ở Bài 44 — API Authentication.

Ở bài này, chúng ta tập trung hiểu cơ chế Sanctum.


9. Token là gì?

Token có thể hiểu đơn giản là:

Một chuỗi bí mật đại diện cho quyền truy cập API của một user.

Ví dụ:

1|K7x9fL2mQ...

Client không cần gửi:

email
password

ở mọi request.

Thay vào đó:

Authorization: Bearer 1|K7x9fL2mQ...

Laravel dùng token để xác định user.


10. Bearer Token

Đây là format phổ biến:

Authorization: Bearer TOKEN

Ví dụ:

Authorization: Bearer 1|K7x9fL2mQ...

Có thể hình dung:

Authorization
      │
      ├── Bearer
      │
      └── Token

Bearer cho biết request đang sử dụng một bearer token để xác thực.


11. Bảo vệ API bằng auth:sanctum

Đây là phần quan trọng nhất.

Ví dụ API:

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

Middleware:

auth:sanctum

sẽ yêu cầu request phải được Sanctum xác thực. Laravel tài liệu hướng dẫn sử dụng middleware này để bảo vệ API routes.


12. API không có token

Client gọi:

GET /api/user

nhưng không gửi:

Authorization: Bearer ...

Laravel sẽ không xác thực được user.

Kết quả thường là:

401 Unauthorized

Có thể hình dung:

GET /api/user
      ↓
auth:sanctum
      ↓
Token?
   NO ↓
  401

13. API có token

Client gửi:

GET /api/user
Authorization: Bearer TOKEN

Sanctum kiểm tra:

Token
  ↓
personal_access_tokens
  ↓
Hợp lệ?
  ↓
User

Sau đó:

$request->user()

sẽ trả về user đã được xác thực.


14. Tạo endpoint /api/user

Trong:

routes/api.php

có thể viết:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

Request:

GET /api/user

Nếu token hợp lệ:

{
    "id": 1,
    "name": "Admin",
    "email": "admin@example.com"
}

15. Không nên trả User Model trực tiếp trong project thực tế

Ở Bài 42 chúng ta đã học API Resource.

Do đó thay vì:

return $request->user();

chúng ta có thể sử dụng:

return new UserResource(
    $request->user()
);

Ví dụ:

use App\Http\Resources\UserResource;
use Illuminate\Http\Request;

Route::get('/user', function (Request $request) {

    return new UserResource(
        $request->user()
    );

})->middleware('auth:sanctum');

Luồng:

Sanctum
   ↓
Authenticated User
   ↓
UserResource
   ↓
JSON

Đây chính là sự kết hợp giữa:

Bài 42
API Resource

+

Bài 43
Sanctum

16. Bảo vệ Posts API

Ở Bài 41 chúng ta có:

Route::apiResource('posts', PostController::class);

Nhưng nếu toàn bộ route đều public thì:

POST
PUT
DELETE

cũng có thể bị gọi bởi người chưa đăng nhập.

Chúng ta có thể tách route.

Ví dụ:

Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);

Route::middleware('auth:sanctum')->group(function () {

    Route::post('/posts', [PostController::class, 'store']);

    Route::put('/posts/{post}', [PostController::class, 'update']);

    Route::patch('/posts/{post}', [PostController::class, 'update']);

    Route::delete('/posts/{post}', [PostController::class, 'destroy']);

});

Khi đó:

GET /api/posts

có thể public.

Nhưng:

POST /api/posts
PUT /api/posts/1
DELETE /api/posts/1

cần authentication.


17. Có thể viết bằng apiResource

Một cách khác:

Route::apiResource('posts', PostController::class)
    ->only(['index', 'show']);

Route::apiResource('posts', PostController::class)
    ->except(['index', 'show'])
    ->middleware('auth:sanctum');

Tuy nhiên với người mới học, cách chia route thành hai nhóm thường dễ nhìn hơn:

Route::get(...);
Route::get(...);

Route::middleware('auth:sanctum')->group(function () {
    // private API
});

18. Public API và Private API

Chúng ta có thể phân chia:

PUBLIC API

GET /api/posts
GET /api/posts/1
GET /api/categories

và:

PRIVATE API

POST /api/posts
PUT /api/posts/1
DELETE /api/posts/1

Private API yêu cầu:

Authentication

Có thể hình dung:

                 API
                  │
        ┌─────────┴─────────┐
        │                   │
      PUBLIC              PRIVATE
        │                   │
      GET                 POST
      GET                 PUT
                          DELETE
                            │
                      auth:sanctum

19. Sanctum Token không phải Password

Một lỗi phổ biến là nhầm:

password

với:

API token

Password dùng để xác thực user trong quá trình đăng nhập.

Token được cấp sau đó có thể được client dùng cho các request API.

Ví dụ:

Email + Password
       ↓
Authentication
       ↓
Sanctum Token
       ↓
API Requests

Không nên đưa password vào mỗi API request.


20. Token có thể có tên

Khi tạo token:

$user->createToken('blog-api');

Tên:

blog-api

chỉ là tên giúp nhận biết token.

Ví dụ user có:

blog-web
iphone
android
desktop

Các token này có thể thuộc cùng một user.

Sanctum hỗ trợ nhiều token trên một user và cho phép quản lý chúng qua relationship tokens.


21. Xem các token của User

User Model sử dụng:

HasApiTokens

thì có thể truy cập:

$user->tokens

Ví dụ:

foreach ($user->tokens as $token) {
    // ...
}

Có thể hình dung:

User
 │
 ├── Token 1
 ├── Token 2
 └── Token 3

Điều này rất hữu ích khi ứng dụng cho phép user đăng nhập từ nhiều thiết bị.


22. Thu hồi một Token

Token có thể bị revoke bằng cách xóa token khỏi database.

Ví dụ token hiện tại:

$request->user()
    ->currentAccessToken()
    ->delete();

Điều này có thể dùng cho:

POST /api/logout

Ví dụ:

Route::post('/logout', function (Request $request) {

    $request->user()
        ->currentAccessToken()
        ->delete();

    return response()->json([
        'message' => 'Logged out successfully'
    ]);

})->middleware('auth:sanctum');

Sau khi token bị xóa:

Token cũ
   ↓
Không còn hợp lệ

Sanctum hỗ trợ revoke token thông qua relationship tokens hoặc currentAccessToken().


23. Thu hồi tất cả Token

Ví dụ user chọn:

Đăng xuất khỏi tất cả thiết bị.

Có thể:

$request->user()
    ->tokens()
    ->delete();

Khi đó:

Token Web
Token Android
Token iPhone
Token Desktop

đều bị revoke.


24. Token Abilities

Sanctum còn hỗ trợ abilities cho token.

Ví dụ tạo token:

$token = $user->createToken(
    'mobile',
    ['posts:read']
);

Token này có ability:

posts:read

Có thể tạo token khác:

$user->createToken(
    'admin',
    [
        'posts:read',
        'posts:create',
        'posts:update',
        'posts:delete'
    ]
);

Khi đó có thể phân biệt:

Read-only token

và:

Full-access token

25. Kiểm tra Ability

Trong request:

if ($request->user()->tokenCan('posts:read')) {
    // ...
}

Ví dụ:

if (! $request->user()->tokenCan('posts:delete')) {
    abort(403);
}

Luồng:

User
 ↓
Token
 ↓
Abilities
 ↓
posts:delete?
 ↓
YES / NO

Đây là một bước tiến từ:

Authentication

sang:

Authorization

Tuy nhiên project Blog CMS của chúng ta đã có:

role
Policy

nên trong dự án thực tế cần thiết kế hai tầng này một cách rõ ràng, thay vì nhồi mọi permission vào token.


26. Token Expiration

Theo mặc định, Sanctum token không tự hết hạn và có thể bị vô hiệu hóa bằng cách revoke token. Sanctum có tùy chọn cấu hình thời gian hết hạn nếu ứng dụng cần.

Ví dụ ý tưởng:

Token
   ↓
Expiration
   ↓
Hết hạn
   ↓
Không sử dụng được

Đây là một điểm cần quan tâm khi xây dựng API production.


27. Sanctum và SPA

Sanctum không chỉ có API token.

Sanctum còn hỗ trợ authentication cho first-party SPA bằng cookie/session.

Đây là điểm rất quan trọng:

API Token

và:

SPA Authentication

là hai cơ chế khác nhau trong Sanctum.

Với SPA của chính bạn, Sanctum có thể sử dụng session cookie thay vì personal access token. Tài liệu Laravel cũng lưu ý không nên dùng API token như cơ chế xác thực cho first-party SPA nếu SPA authentication của Sanctum phù hợp hơn.


28. API Token và SPA Cookie khác nhau

API Token

Mobile App
    ↓
Bearer Token
    ↓
Laravel
    ↓
Sanctum

Phù hợp với:

Mobile
Third-party API
Personal API token

SPA Authentication

React / Vue SPA
       ↓
Session Cookie
       ↓
Laravel
       ↓
Sanctum

Phù hợp với:

First-party SPA

Không nên học hai cơ chế này như thể chúng là cùng một thứ.


29. statefulApi()

Nếu sử dụng Sanctum cho first-party SPA, Laravel có middleware hỗ trợ stateful API.

Trong bootstrap/app.php có thể cấu hình:

->withMiddleware(function (Middleware $middleware): void {
    $middleware->statefulApi();
})

Cơ chế này cho phép các request từ SPA của bạn được xác thực bằng session cookie, trong khi Sanctum vẫn có thể xác thực API token cho các request thích hợp.

Phần này chúng ta chỉ cần biết ở Bài 43.

Không cần triển khai SPA authentication đầy đủ ở đây.


30. Sanctum trong Blog CMS

Project của chúng ta hiện có:

users
categories
posts

và:

role
is_active

API có thể thiết kế:

PUBLIC

GET /api/posts
GET /api/posts/1
GET /api/categories

PRIVATE:

POST /api/posts
PUT /api/posts/1
DELETE /api/posts/1

Sau Sanctum:

Client
   ↓
Bearer Token
   ↓
Sanctum
   ↓
User
   ↓
Controller
   ↓
Policy
   ↓
Post

Đây mới là kiến trúc hợp lý.


31. Sanctum không thay thế Policy

Ví dụ:

User #10

đã đăng nhập thành công.

Sanctum xác nhận:

User #10 là người đang gọi API.

Nhưng User #10 có được sửa Post #100 hay không?

Đó là câu hỏi khác.

Ta cần:

Policy

Ví dụ:

Sanctum
   ↓
Authentication
   ↓
User #10
   ↓
PostPolicy
   ↓
Được sửa?

Vì vậy:

Sanctum ≠ Permission

32. auth:sanctum + Policy

Ví dụ:

Route::put('/posts/{post}', [
    PostController::class,
    'update'
])
->middleware('auth:sanctum');

Controller:

public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);

    // ...
}

Luồng:

Request
   ↓
auth:sanctum
   ↓
User authenticated?
   ↓
YES
   ↓
Policy
   ↓
Có quyền update?
   ↓
YES
   ↓
Update Post

Đây là kiến trúc authentication + authorization rất quan trọng.


33. Test Sanctum bằng Postman

Sau khi có token:

TOKEN = 1|abc123...

Trong Postman:

Authorization

chọn:

Bearer Token

nhập:

1|abc123...

Postman sẽ gửi:

Authorization: Bearer 1|abc123...

Sau đó gọi:

GET /api/user

Nếu token hợp lệ:

200 OK

Nếu token không hợp lệ:

401 Unauthorized

34. Không gửi Token trong URL

Không nên:

/api/posts?token=abc123

Không nên:

/api/user/abc123

Thay vào đó:

Authorization: Bearer TOKEN

Token là thông tin nhạy cảm.

Không nên đưa token vào:

URL
HTML
log
screenshot
Git repository

35. Không commit token vào Git

Ví dụ tuyệt đối không viết:

$token = '1|abc123...';

rồi commit lên Git.

Không nên lưu token thật trong:

source code

hoặc:

README

hoặc:

.env.example

Token thật phải được bảo vệ.

Nếu token bị lộ:

Revoke token
↓
Tạo token mới

36. Một ví dụ hoàn chỉnh

routes/api.php:

<?php

use App\Http\Controllers\Api\PostController;
use App\Http\Resources\UserResource;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/user', function (Request $request) {
    return new UserResource($request->user());
})->middleware('auth:sanctum');

Route::get('/posts', [
    PostController::class,
    'index'
]);

Route::get('/posts/{post}', [
    PostController::class,
    'show'
]);

Route::middleware('auth:sanctum')->group(function () {

    Route::post('/posts', [
        PostController::class,
        'store'
    ]);

    Route::put('/posts/{post}', [
        PostController::class,
        'update'
    ]);

    Route::patch('/posts/{post}', [
        PostController::class,
        'update'
    ]);

    Route::delete('/posts/{post}', [
        PostController::class,
        'destroy'
    ]);

    Route::post('/logout', function (Request $request) {

        $request->user()
            ->currentAccessToken()
            ->delete();

        return response()->json([
            'message' => 'Logged out successfully'
        ]);
    });
});

Kiến trúc:

PUBLIC
│
├── GET /api/posts
└── GET /api/posts/{post}

PRIVATE
│
├── GET /api/user
├── POST /api/posts
├── PUT /api/posts/{post}
├── PATCH /api/posts/{post}
├── DELETE /api/posts/{post}
└── POST /api/logout

37. Kiểm tra route

Chạy:

php artisan route:list --path=api

Có thể thấy:

GET|HEAD   api/posts
POST       api/posts
GET|HEAD   api/posts/{post}
PUT        api/posts/{post}
PATCH      api/posts/{post}
DELETE     api/posts/{post}
GET|HEAD   api/user
POST       api/logout

Các route private sẽ có middleware:

auth:sanctum

38. Luồng Authentication hoàn chỉnh

Hiện tại chúng ta có thể hình dung:

                 USER
                   │
                   ▼
              Login API
                   │
                   ▼
              Sanctum Token
                   │
                   ▼
          ┌─────────────────┐
          │    API Client   │
          └─────────────────┘
                   │
                   │ Bearer Token
                   ▼
              Laravel API
                   │
                   ▼
             auth:sanctum
                   │
          ┌────────┴────────┐
          │                 │
       Invalid            Valid
          │                 │
          ▼                 ▼
        401              User
                            │
                            ▼
                         Policy
                            │
                            ▼
                       Controller
                            │
                            ▼
                          Model
                            │
                            ▼
                         Resource
                            │
                            ▼
                           JSON

Đây là kiến trúc mà chúng ta sẽ hoàn thiện ở Bài 44.


39. Bài tập thực hành

Bài tập 1 — Cài Sanctum

Chạy:

php artisan install:api

Sau đó:

php artisan migrate

Bài tập 2 — User Model

Thêm:

use Laravel\Sanctum\HasApiTokens;

và:

use HasApiTokens, HasFactory, Notifiable;

Bài tập 3 — Protected API

Tạo:

GET /api/user

với:

->middleware('auth:sanctum')

Bài tập 4 — Test không có Token

Gọi:

GET /api/user

Không gửi Authorization.

Quan sát:

401 Unauthorized

Bài tập 5 — Test bằng Bearer Token

Gửi:

Authorization: Bearer YOUR_TOKEN

Sau đó gọi:

GET /api/user

Quan sát user trả về.


Bài tập 6 — Logout

Tạo:

POST /api/logout

Sau khi logout:

Token cũ
   ↓
Không còn hợp lệ

Bài tập 7 — Bảo vệ Post API

Public:

GET /api/posts
GET /api/posts/1

Private:

POST /api/posts
PUT /api/posts/1
DELETE /api/posts/1

40. Những điều cần nhớ

Sanctum dùng để làm gì?

API Authentication
SPA Authentication
Mobile Authentication
API Tokens

User Model:

use Laravel\Sanctum\HasApiTokens;

Tạo token:

$user->createToken('blog-api');

Lấy token plaintext:

$token->plainTextToken

Gửi token:

Authorization: Bearer TOKEN

Bảo vệ route:

->middleware('auth:sanctum')

User hiện tại:

$request->user()

Token hiện tại:

$request->user()->currentAccessToken()

Revoke token:

$request->user()
    ->currentAccessToken()
    ->delete();

Revoke tất cả token:

$request->user()
    ->tokens()
    ->delete();

41. Authentication ≠ Authorization

Hãy nhớ thật kỹ:

SANCTUM
   ↓
Authentication
   ↓
"Bạn là ai?"

Còn:

POLICY / GATE / PERMISSION
   ↓
Authorization
   ↓
"Bạn được phép làm gì?"

Ví dụ:

User đăng nhập
       ↓
Sanctum
       ↓
User #5
       ↓
Policy
       ↓
User #5 có được xóa Post #10?

Hai vấn đề này phải được xử lý riêng.


🎯 Tổng kết Bài 43

Sau Bài 41:

REST API

Sau Bài 42:

API Resource

Bây giờ Bài 43:

Sanctum

Chúng ta đã xây dựng được:

                    Laravel 13 API

                         │
                 ┌───────┴───────┐
                 │               │
              Public           Private
                 │               │
              GET API        auth:sanctum
                                 │
                              Token
                                 │
                              User

API bây giờ không còn là:

Ai cũng gọi được

mà đã có:

Client
   ↓
Bearer Token
   ↓
Sanctum
   ↓
Authenticated User

Tuy nhiên chúng ta vẫn còn một mảnh ghép rất quan trọng:

User lấy token bằng cách nào?

Chúng ta cần một API:

POST /api/login

Client gửi:

{
    "email": "admin@example.com",
    "password": "password"
}

Laravel kiểm tra:

Email
+
Password

Nếu chính xác:

        ↓
    Sanctum
        ↓
    API Token
        ↓
     Client

Sau đó client dùng token để gọi:

GET    /api/user
GET    /api/posts
POST   /api/posts
PUT    /api/posts/1
DELETE /api/posts/1

Đó chính là nội dung tiếp theo:

🔥 BÀI 44 — API AUTHENTICATION

Chúng ta sẽ hoàn thiện:

Register API
Login API
Logout API
Bearer Token
auth:sanctum
User API
Validation
401 Unauthorized
403 Forbidden
Token revoke
API Resource

và ghép tất cả thành một API Authentication hoàn chỉnh cho Blog CMS Laravel 13.

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

Đăng nhận xét

Facebook Youtube RSS