Giới thiệu Authentication trong Laravel:
Laravel cung cấp tính năng xác thực và login nhanh chóng, bảo mật và dễ dàng thực thi. Trong core Laravel, xác thực được cấu thành từ “guards” và “providers”. Guards định nghĩa cách user được xác thực trên mỗi request. Ví dụ session guard lưu trữ trạng thái sử dụng session storage và cookies. Provider định nghĩa cách user được truy xuất từ storage. Laravel sử dụng Eloquent và database query builder. Bạn cũng có thể định nghĩa thêm providers nếu cần.
Cấu hình xác thực trong file config/auth.php.
Guards và providers không nên nhầm lẫn với “roles” và “permissions”. Về xác minh phân quyền (authorization) xem ở bài tiếp theo.
Starter Kits:
Cài đặt 1 starter kit, sau đó migrate database và đăng ký user mới ở link /register.
Sử dụng Breeze cung cấp xác thực controller, route, view. Nếu không sử dụng Breeze, bạn cũng có thể review để học cách xây dựng xác thực trong dự án thực tế.
Cân nhắc về CSDL:
Mặc định Laravel include Eloquent App\Model\User. Model này được sử dụng với Eloquent authentication driver. Nếu bạn không định sử dụng Eloquent, bạn có thể sử dụng database authentication provider cái mà sử dụng query builder.
Khi build database schema cho App\Model\User model, chắc chắn rằng cột password dài ít nhất là 60 ký tự. Tất nhiên table users migration đã được tạo sẵn với cột password đáp ứng yêu cầu trên.
Thêm nữa table users cũng cần 1 column remember_token (nullable) với 100 ký tự. Cột này để lưu “remember me”. Tất nhiên migration mặc định cũng đã có sẵn option này.
Tổng quan hệ thống Authentication:
Laravel có sẵn hệ thống xác thực và session services được tiếp cận thông qua Auth và Session facades. Những services này cung cấp tính năng xác thực thông qua cookie, cho phép bạn xác minh thông tin user và xác thực user. Ngoài ra những services này sẽ tự động lưu trữ dữ liệu xác thực phù hợp trong phiên của user và cung cấp cookie cho user.
Bắt đầu với Starter Kit:
3 packages có thể tìm hiểu là Laravel Breeze, Laravel Jetstream, Laravel Fortify.
Laravel Breeze là thực thi nhỏ gọn của Laravel bao gồm các tính năng login, register, password reset, email verification, password confirmation. Breeze sử dụng Blade templates và Tailwind CSS. Cài đặt Breeze trên ứng dụng Laravel mới khởi tạo bằng lệnh:
composer require laravel/breeze --devKhi đã cài đặt Breeze, chạy lệnh php artisan breeze:install. Lệnh này sẽ tạo ra authentication views, routes, controllers, và các resources khác:
php artisan breeze:install
php artisan migrate
npm install
npm run devBreeze & Blade:
Mặc định Breeze sử dụng blade, lệnh breeze:install sẽ tự cài đặt mà không cần thêm option nào khác. Sau khi cài đặt bạn cần compile các frontend assets:
php artisan migrate
npm install
npm run devĐăng nhập vào /login, đăng ký /register. Các routes được khai báo trong routes/auth.php.
Authentication Quickstart:
Truy xuất user đã xác thực:
Sử dụng method user của facade Auth:
use Illuminate\Support\Facades\Auth;
// Retrieve the currently authenticated user...
$user = Auth::user();
// Retrieve the currently authenticated user's ID...
$id = Auth::id();Hoặc cách khác, khi user đã xác thực, tiếp cận thông tin qua instance Request:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FlightController extends Controller
{
/**
* Update the flight information for an existing flight.
*/
public function update(Request $request): RedirectResponse
{
$user = $request->user();
// ...
return redirect('/flights');
}
}Xác minh user đã xác thực hay chưa:
Dùng method check của Auth. Method này trả về true nếu user đã đăng nhập:
use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// The user is logged in...
}Mặc dù có thể xác định user đã đăng nhập hay chưa bằng method check, bạn vẫn cần sử dụng middleware để verify user trước khi cho phép user tiếp cận route/controller.
Protecting Routes:
Laravle cung cấp middleware auth, tham chiếu đến Illuminate\Auth\Middleware\Authenticate class. Add middleware vào route define:
Route::get('/flights', function () {
// Only authenticated users may access this route...
})->middleware('auth');Redirect user chưa xác thực:
Khi user chưa xác thực, middleware auth sẽ redirect user đến login route. Bạn có thể thay đổi bằng cách update function redirectTo ở trong file app/Http/Middleware/Authenticate.php:
use Illuminate\Http\Request;
/**
* Get the path the user should be redirected to.
*/
protected function redirectTo(Request $request): string
{
return route('login');
}Chỉ định Guard:
Khi gắn middleware auth vào route, bạn có thể chỉ định “guard” nào có thể sử dụng để xác thực user. Guard được chỉ định nên tương ứng với 1 trong số keys ở array guards trong file cấu hình auth.php.
Route::get('/flights', function () {
// Only authenticated users may access this route...
})->middleware('auth:admin');Login Throttling:
Nếu bạn sử dụng Breeze hoặc Jetstream, rate limit sẽ tự động được áp dụng cho các lần thử login. Mặc định, user sẽ không thể đăng nhập trở lại trong vòng 1 phút nếu đăng nhập sai sau 1 số lần nhất định. Cơ chế kiểm soát dựa trên username / email và IP address.
Xác thực bằng tay:
Nếu bạn không muốn sử dụng starter kits mà tự xử lý bằng tay. Vẫn sử dụng Auth, import facade này vào class. Tiếp theo, xem method attempt. Method này sử dụng để xử lý đăng nhập. Nếu thành công, bạn nên khởi tạo lại session để ngăn chặn tấn công session fixation:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Handle an authentication attempt.
*/
public function authenticate(Request $request): RedirectResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
])->onlyInput('email');
}
}Method attempt chấp nhận 1 array key / value làm tham số thứ nhất. Value trong array để tìm user trong database. Nếu user được tìm thấy, hashed password trong database sẽ được so sánh với password (Laravel sẽ tự hash password để so sánh). Session mới sẽ được khởi tạo.
Laravel authentication service sẽ truy xuất user từ database dựa trên cấu hình guard trong file config/auth.php, Eloquent được chỉ định và nó sử dụng model App\Models\Users để truy xuất user. Bạn có thể thay đổi những cấu hình trong file này.
Method intended sẽ redirect user đến URL mà họ đã thử tiếp cận trước khi đăng nhập. 1 URL dự phòng có thể được cung cấp cho method này trong trường hợp intended URL không có.
Chỉ định các điều kiện bổ sung:
Bạn có thể thêm điều kiện query vào authenticate query, ví dụ điều kiện user đã active:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// Authentication was successful...
}Với query phức tạp, sử dụng closure:
use Illuminate\Database\Eloquent\Builder;
if (Auth::attempt([
'email' => $email,
'password' => $password,
fn (Builder $query) => $query->has('activeSubscription'),
])) {
// Authentication was successful...
}Method attemptWhen dùng để chỉ xác thực user tiềm năng sau khi user đã đáp ứng 1 yêu cầu trước (ví dụ user chưa bị ban nick). Method nhận 1 closure làm tham số thứ 2, closure này nhận tham số user và trả về true hoặc fail:
if (Auth::attemptWhen([
'email' => $email,
'password' => $password,
], function (User $user) {
return $user->isNotBanned();
})) {
// Authentication was successful...
}Tiếp cận Guard instance cụ thể:
Thông qua guard method, bạn có thể chỉ định guard nào sẽ được sử dụng khi xác thực user. Điều này cho phép bạn quản lý xác thực cho các phần riêng biệt trong ứng dụng bằng cách sử dụng các model authenticate hoặc table user riêng biệt. Tên guard pass cho method guard phải tương ứng với 1 trong các guards đã cấu hình trong file config/auth.php:
if (Auth::guard('admin')->attempt($credentials)) {
// ...
}Remember me:
Để sử dụng tính năng này, pass 1 giá trị boolean làm tham số thứ 2 cho method attempt. Khi value được set true, Laravel sẽ giữ user vô thời hạn cho đến khi đăng xuất bằng tay. Bảng users cần có cột remember_token:
use Illuminate\Support\Facades\Auth;
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// The user is being remembered...
}Bạn có thể dùng method viaRemember để xác định xem user đã login có sử dụng “remember me” hay không:
use Illuminate\Support\Facades\Auth;
if (Auth::viaRemember()) {
// ...
}Một số method xác thực khác:
Xác thực 1 user instance:
Nếu bạn muốn 1 user đang tồn tại được đăng nhập, truyền user instance vào method login. User instance phải implement class contract Illuminate\Contracts\Auth\Authenticatable. Model App\Models\User đã implement interface này. Method này hữu ích sau khi user đã đăng ký thành công và bạn muốn user tự động đăng nhập luôn:
use Illuminate\Support\Facades\Auth;
Auth::login($user);Bạn có thể pass tham số thứ 2 để “remember me”:
Auth::login($user, $remember = true);Nếu cần thiết, bạn có thể chỉ định authentication guard trước khi gọi login:
Auth::guard('admin')->login($user);Xác thực 1 user bằng ID:
Để đăng nhập 1 user sử dụng ID trong database, sử dụng method loginUsingId:
Auth::loginUsingId(1);Bạn có thể pass tham số thứ 2 để lưu “remember me”.
HTTP Basic Authentication:
HTTP Basic Authentication cung cấp cách thức nhanh chóng để đăng nhập mà không cần set up login page. Để bắt đầu, gắn auth.basic middleware vào route. Middleware này được include sẵn trong Laravel nên không cần define:
Route::get('/profile', function () {
// Only authenticated users may access this route...
})->middleware('auth.basic');Một khi middleware được gắn vào route, user cần xác minh khi tiếp cận những route này. Mặc định auth.basic middleware giả sử cột email trong database là “username”.
Log Out:
Sử dụng method logout của facade Auth. Ngoài ra bạn cũng nên vô hiệu hóa session và khởi tạo lại CSRF token:
public function logout(Request $request): RedirectResponse
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}Vô hiệu hóa session trên thiết bị khác:
Laravel cung cấp cơ chế để vô hiệu hóa session và log out trên thiết bị khác mà không ảnh hưởng trên thiết bị hiện tại. Đầu tiên chắc chắn rằng Illuminate\Session\Middleware\AuthenticateSession middleware được include vào route mà sẽ nhận session authentication. Thông thường bạn nên đặt middleware này vào route group để apply được cho nhiều routes. Mặc định AuthenticateSession middleware có thể gắn vào 1 route sử dụng auth.session alias (đã define trong kernel):
Route::middleware(['auth', 'auth.session'])->group(function () {
Route::get('/', function () {
// ...
});
});Sau đó dùng method logoutOtherDevices. Method này yêu cầu user nhập lại password:
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($currentPassword);Password confirmation:
Thi thoảng bạn sẽ cần user phải confirm password để thực hiện 1 tác vụ nào đó. Laravel có sẵn middleware hỗ trợ công việc này một cách dễ dàng. Cần 2 route: 1 để hiển thị view yêu cầu user nhập mật khẩu, 1 route để confirm password hợp lệ.
Sau khi confirm password, hệ thống sẽ không bắt user confirm lần nữa trong vòng 3 tiếng. Bạn có thể cấu hình thời gian này ở password_timeout trong file config/auth.php.
Routing:
Form password confirm:
Route::get('/confirm-password', function () {
return view('auth.confirm-password');
})->middleware('auth')->name('password.confirm');Confirm the password:
Define 1 route xử lý form request từ view trên:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redirect;
Route::post('/confirm-password', function (Request $request) {
if (! Hash::check($request->password, $request->user()->password)) {
return back()->withErrors([
'password' => ['The provided password does not match our records.']
]);
}
$request->session()->passwordConfirmed();
return redirect()->intended();
})->middleware(['auth', 'throttle:6,1']);Method passwordConfirmed sẽ set timestamp vào session để xác định lần cuối confirm password của user. Sau đó redirect đến intended URL.
Protecting Routes:
Bạn cần chắc chắn rằng tất cả các action mà yêu cầu password confirm đều được gắn middleware password.confirm. Middleware này sẵn có và sẽ tự động lưu intended URL trong session nhờ đó user có thể redirect đến sau khi đã confirm password.
Route::get('/settings', function () {
// ...
})->middleware(['password.confirm']);
Route::post('/settings', function () {
// ...
})->middleware(['password.confirm']);Thêm custom Guard:
Bạn có thể tự define custom authentication guards sử dụng extend method của Auth. Bạn nên đặt method extend trong AuthServiceProvider:
<?php
namespace App\Providers;
use App\Services\Auth\JwtGuard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application authentication / authorization services.
*/
public function boot(): void
{
Auth::extend('jwt', function (Application $app, string $name, array $config) {
// Return an instance of Illuminate\Contracts\Auth\Guard...
return new JwtGuard(Auth::createUserProvider($config['provider']));
});
}
}Hàm callback truyền vào method extend sẽ trả về 1 implement của Illuminate\Contracts\Auth\Guard. Interface này có các method bạn cần để define 1 custom guard. Khi đã define guard, tham chiếu nó trong file cấu hình auth.php:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],Closure request guards:
Cách đơn giản nhất để thực thi 1 custom, HTTP request dựa trên hệ thống xác thực là sử dụng Auth::viaRequest method. Method này cho phép bạn nhanh chóng define quy trình xác thực của bạn bằng 1 closure.
Để bắt đầu, gọi method Auth::viaRequest trong boot method của AuthServiceProvider. Method này chấp nhận 1 driver xác thực làm tham số thứ nhất. Tên này có thể là string bất kỳ mô tả custom guard của bạn. Tham số thứ 2 là closure nhận 1 request HTTP và trả về 1 user instance, hoặc nếu xác thực fail thì trả về null:
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Register any application authentication / authorization services.
*/
public function boot(): void
{
Auth::viaRequest('custom-token', function (Request $request) {
return User::where('token', (string) $request->token)->first();
});
}Khi custom authentication driver được define, bạn có thể cấu hình nó như 1 driver trong guards config trong file cấu hình auth.php:
'guards' => [
'api' => [
'driver' => 'custom-token',
],
],Cuối cùng bạn có thể tham chiếu guard khi gán middleware xác thực vào 1 route:
Route::middleware('auth:api')->group(function () {
// ...
}Thêm 1 custom User providers:
Nếu bạn không sử dụng database quan hệ truyền thống để lưu trữ users, bạn cần extend Laravel với user provider của bạn. Sử dụng method provider trong Auth để tạo 1 custom user provider. User provider resolver sẽ trả về 1 implement của Illuminate\Contracts\Auth\UserProvider:
namespace App\Providers;
use App\Extensions\MongoUserProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application authentication / authorization services.
*/
public function boot(): void
{
Auth::provider('mongo', function (Application $app, array $config) {
// Return an instance of Illuminate\Contracts\Auth\UserProvider...
return new MongoUserProvider($app->make('mongo.connection'));
});
}
}Sau khi đã đăng ký provider sử dụng method provider, bạn có thể chuyển đến user provider mới trong file auth.php. Đầu tiên, define 1 provider sử dụng driver mới của bạn:
'providers' => [
'users' => [
'driver' => 'mongo',
],
],Cuối cùng, tham chiếu provider này trong cấu hình guards:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],User Provider Contract:
Illuminate\Contracts\Auth\UserProvider có trách nhiệm lấy 1 thực thi Illuminate\Contracts\Auth\Authenticatable bên ngoài 1 hệ thống lưu trữ cố định như MySQL, MongoDB … 2 interface này cho phép Laravel authentication tiếp tục chức năng bất kể user data được lưu như thế nào hay kiểu class được sử dụng đại diện cho authenticated user:
Xem Illuminate\Contracts\Auth\UserProvider contract:
<?php
namespace Illuminate\Contracts\Auth;
interface UserProvider
{
public function retrieveById($identifier);
public function retrieveByToken($identifier, $token);
public function updateRememberToken(Authenticatable $user, $token);
public function retrieveByCredentials(array $credentials);
public function validateCredentials(Authenticatable $user, array $credentials);
}Function retrieveById nhận 1 key đại diện cho user (ví dụ ID). Authenticatable khớp với ID sẽ được truy xuất và trả về bởi method.
Method retrieveByToken truy xuất 1 user bởi $identifier và remember me $token, thông thường lưu trữ ở database column remember_token. Cũng như method trên, 1 Authenticatable sẽ được trả về.
Authenticatable Contract:
Giờ ta sẽ xem xét các method của UserProvider. Trước hết xem contract Authenticatable. Lưu ý, user provider trả về 1 thực thi của interface này từ các method retrieveById, retrieveByToken, retrieveByCredentials:
<?php
namespace Illuminate\Contracts\Auth;
interface Authenticatable
{
public function getAuthIdentifierName();
public function getAuthIdentifier();
public function getAuthPassword();
public function getRememberToken();
public function setRememberToken($value);
public function getRememberTokenName();
}Interface này đơn giản. Method getAuthIdentifierName trả về tên của trường “primary key” của user. Method getAuthPassword trả về hashed password. Interface này cho phép hệ thống làm việc với bất kỳ “user” class nào, bất kể ORM nào hoặc lớp trừu tượng storage nào bạn đang sử dụng. Mặc định, Laravel có sẵn App\Models\User class trong thư mục model thực thi interface này.
Events:
Laravel kích hoạt nhiều sự kiện trong quá trình xác thực. Bạn có thể attach listeners cho những sự kiện này trong EventServiceProvider:
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'Illuminate\Auth\Events\Registered' => [
'App\Listeners\LogRegisteredUser',
],
'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],
'Illuminate\Auth\Events\Authenticated' => [
'App\Listeners\LogAuthenticated',
],
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
'Illuminate\Auth\Events\Failed' => [
'App\Listeners\LogFailedLogin',
],
'Illuminate\Auth\Events\Validated' => [
'App\Listeners\LogValidated',
],
'Illuminate\Auth\Events\Verified' => [
'App\Listeners\LogVerified',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
'Illuminate\Auth\Events\CurrentDeviceLogout' => [
'App\Listeners\LogCurrentDeviceLogout',
],
'Illuminate\Auth\Events\OtherDeviceLogout' => [
'App\Listeners\LogOtherDeviceLogout',
],
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
'Illuminate\Auth\Events\PasswordReset' => [
'App\Listeners\LogPasswordReset',
],
];