Authentication trong Laravel


Phần lớn các website đều phải xác thực khi thực hiện một hành vi nào đó trên hệ thống. Hôm nay cùng mình tìm hiểu về Authentication trong Laravel - nó giúp xây dựng cho việc thực hiện xác thực vô cùng đơn giản và nhanh chóng. Nhiều khi không cần hiểu hết vẫn có thể sử dụng dễ dàng. Bạn sẽ không ngờ rằng Laravel đã dọn sẵn dân tận “mồm” cho chúng ta, chỉ cần há miệng và ăn thôi hà. Bắt tay ngay thôi nào!

Giới thiệu

Như phía trên mình đã giới thiệu về Authentication mà Laravel đã cung cấp cho chúng ta. Ta bắt tay vào tìm hiểu một số khái niệm quan trọng sau.

Trước tiên đi nhẹ vào phần config của nó tại config/auth.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that the reset token should be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 10800,

];

Hệ thống xác thực Authentication của Laravel được xây dựng dựa trên 2 thành phần cốt lõi - guard và provider.

Guards

Guard các bạn cứ hiểu như là một cách cung cấp logic được dùng để xác thức người dùng. Trong Laravel thường hay dùng session guard hoặc token guard.

  • Session guard duy trì trạng thái người dùng trong mỗi lần request bằng cookie.
  • Token guard xác thực người dùng bằng cách kiểm tra token hợp lệ trong mỗi lần request.

Như bạn thấy, guard xác định logic của việc xác thực và không cần thiết để “luôn xác thực” bằng cách lấy các thông tin hợp lệ từ phía back-end. Bạn có thể triển khai một guard mà chỉ cần kiểm tra sự có mặt của một thông tin cụ thể trong headers của request và xác thực người dùng dựa trên điều đó.

Providers

Nếu Guards hỗ trợ việc định nghĩa logic để xác thực thì Providers lấy ra dữ liệu người dùng từ phía back-end. Nếu guards yêu cầu người dùng phải hợp lệ với bộ lưu trữ ở back-end thì việc triển khai truy suất người dùng sẽ được providers thực hiện. Laravel hỗ trợ cho việc người dùng truy suất sử dụng Eloquent và Query Buider vào database.
Ví dụ nhé, các bạn không muốn đặt model User trong namespace App\Model nữa mà các bạn muốn đặt trong namespace App thì chúng ta sẽ thay đổi providers trong file app/auth.php như sau :

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

Database schema của bảng users

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->rememberToken();
    $table->timestamps();
});

 // support 
public function rememberToken()
{
    return $this->string('remember_token', 100)->nullable();
}

Lưu ý: Khi xây dựng database schema cho model User, đảm bảo rằng độ dài cột password tổi thiểu 60 kí tự, mặc định 255 kí tự.
Cột remember_token dùng để lưu token để giành cho chức năng Remember me.
Đừng lo lắng nhé nếu mà chúng ta cứ sử dụng như default thì chúng ta chẳng cần động vào file config/auth.php này làm gì !!!

Authentication với Laravel ui

Để bắt đầu nhanh chúng ta sử dụng package laravel/ui. Chạy lệnh composer:

composer require laravel/ui

Lưu ý: Tùy vào version của Laravel và sử dụng package laravel/ui tương ứng.

Ver | Laravel Ver
1.x | 5.8, 6.x
2.x | 7.x
3.x | 8.x

Sau khi hoàn tất quá trình cài đặt, chạy lệnh command để sinh ra các file cần thiết (view, controller, route):

php artisan ui:auth

php artisan ui:controllers

Laravel sinh ra một số controller của Authentication đủ để có thể hoạt động. Chúng nằm trong folder app/Http/Controller/Auth

Phần views trong folder resources/views/auth

Cài thư viện bootstrap để triển khai nhanh chóng website cho đẹp mắt bằng lệnh hoặc download trực tiếp trên trang chủ của nó.

php artisan ui bootstrap

// Sau đó tiếp tục chạy lệnh sau khi xuất hiện dòng này
Bootstrap scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.

Laravel đã khai báo sẵn cho chúng ta, chỉ cần chạy lệnh và mở ra chạy thôi, quá eazy phải không nào!

Cuối cùng phải chạy migrate để sinh ra bảng userspassword_resets

php artisan migrate

Controller

Laravel “đẻ” ra một vài controller đủ để chúng ta có thể sử dụng cho nhiều trường hợp. Chúng ta cùng điểm qua chức năng của các controller này làm gì nhá!!!

ConfirmPasswordController
ConfirmPasswordController dùng để xác nhận lại mật khẩu sau một khoảng thời gian nhất định (mặc định là 3 tiếng).

Mình sẽ nói ra một số nguyên lý của thằng này để các bạn có thể hiểu rõ hơn cách nó hoạt động.

  • Đầu tiên ta cần quan tâm password_timeout trong file config/auth.ph. Đây chính là thời gian sử dụng website trước lần xác thực kế tiếp - nghĩa là sau 3 tiếng (tùy vào config) phải confirm 1 lần.

  • Sau đó phải áp dụng middleware password.confirm vào route mà các bạn muốn xác thực.

    Route::middleware('password.confirm')->get('/home', [HomeController::class, 'index'])->name('home');
    
  • Middleware này được khai báo trong file app/Http/Kernel.php. Bạn vào file RequirePassword.php để theo dõi code tốt hơn nha.

    protected $routeMiddleware = [
      ...
      'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
      ....
    
  • Nguyên lý nó như sau: Sau mỗi lần xác nhận mật khẩu, hệ thống sẽ lưu thời gian xác nhận vào session. Trong middleware sẽ kiểm tra “thời gian hiện tại” trừ đi “thời gian được lưu” trong session. Nếu lớn hơn thời gian trong config thì phải xác nhận lại mật khẩu. Nhỏ hơn hoặc bằng thì sẽ được đi qua middleware.

ForgotPasswordController
ForgotPasswordController dùng để cho điều khiển cho chức năng khi người dùng quên mật khẩu, hệ thống sẽ bắt người dùng nhập mail đã đăng ký với hệ thống để gửi mail tạo mật khẩu mới.


ResetPasswordController
ResetPasswordController làm nhiệm vụ điều khiển khi một user muốn thay đổi mật khẩu tài khoản đăng nhập hệ thống.

Sau khi khôi phục bạn có thể đăng nhập vào hệ thống bằng mật khẩu mới

LoginController
LoginController làm nhiệm vụ điều khiển khi người dùng đăng nhập vào hệ thống. Người dùng sẽ đăng nhập bằng mail với mật khẩu khi đăng kí với hệ thông.

RegisterController
RegisterController làm nhiệm vụ đăng ký một thành viên mới trong hệ thống.

VerificationController
VerificationController dùng để xác thực email sau khi đăng ký. Controller này thì hơi rắc rối hơn 1 xíu.
Để sử dụng được controller này chúng ta phải khai báo như sau:

  • Mở file routes/web.php và thêm verify vào

    Auth::routes(['verify' => true]);
    
  • Tiếp tục mở model dùng để xác thực, ở đây mình dùng mặc định app/Models/User.php. Mở lên và khai báo thêm implements MustVerifyEmail

    class User extends Authenticatable implements MustVerifyEmail
    {
      use HasApiTokens, HasFactory, Notifiable;
    
      /**
       * The attributes that are mass assignable.
       *
       * @var string[]
       */
      protected $fillable = [
          'name',
          'email',
          'password',
          'gender',
      ];
      ...
    }
    

    Sau khi đăng ký thành công tài khoản vào hệ thống, bạn sẽ nhận được email để xác thực.

Sau khi verify xong, database sẽ cập nhật thời gian tại cột email_verified_at trong bảng users. Chúng ta có thể tự tạo Middleware để kiểm tra verify, và sử dụng cho những route nào mà các bạn muốn.

Giả sử chưa nhận được email để xác nhận hoặc email đã hết hạn xác thực, bạn chuyển hướng người dùng qua route('verification.notice') hay đường dẫn http://127.0.0.1:8000/email/verify

Tùy biến Path trong controller

Ở trong các controller phía trên (ngoại trừ ForgotPasswordController dùng để gửi mail) đều khai báo thuộc tính $redirectTo, thuộc tính này giúp ta điều chỉnh đường dẫn mà khi thực hiện xong công việc trong controller sẽ chuyển hướng đến. Mặc định là /home:

protected $redirectTo = RouteServiceProvider::HOME;

Vào file RouteServiceProvider.php sẽ thấy khai báo constant HOME với giá trị là ‘/home’

Khi user không xác thực thành công, chúng ta sẽ chuyển hướng về trang đăng nhập. Chúng ta cũng có thể điều chỉnh trong middleware RedirectIfAuthenticated

public function handle($request, Closure $next, $guard = null)
{
    if (Auth::guard($guard)->check()) {
        return redirect('/home');
    }

    return $next($request);
}

Tùy biến Username, Guard

Theo mặc định của Laravel, để đăng nhập chúng ta sử dụng email. Nếu muốn tùy chỉnh thì chúng ta có thể vào LoginController và định nghĩa method username() để override trait AuthenticatesUsers được gọi trong controller

public function username()
{
    return 'username';
}

Chúng ta cũng có thể tùy biến guard - sử dụng để xác thực user. Để bắt đầu, định nghĩa phương thức guard() trong LoginController, RegisterController, ResetPasswordController để override trait được sử dụng bên trong nó. Hàm này sẽ trả về một instance guard.

use Illuminate\Support\Facades\Auth;

protected function guard()
{
    return Auth::guard('guard-name');
}

Tùy biến Validate, Storage khi đăng ký

Trong RegisterController các bạn cũng có thể customize lại một thành viên mới đăng ký như nào. Các bạn có thể thêm trường cho bảng users để lưu nhiều thông tin về user khi đang ký hơn. Mặc định bảng users khi được tạo ra gồm các trường:

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
    ...
}

Các bạn có thể dùng command tạo migrate mới để thêm một số field vào bảng users

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('address');
        $table->string('phone');
        $table->integer('role');
    });
}

Tiếp tục chúng ta sẽ cho hiển thị thêm 2 trường input để nhập address và phone của người dùng, nhớ thêm các trường vừa thêm trong biến $fillable của model User nhé. Sau đó trong RegisterController chúng ta sẽ cập nhật như sau :

protected function validator(array $data)
{
    return Validator::make($data, [
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => 'required|string|min:6|confirmed',
        // thêm validate cho các trường vừa thêm 
        'address' => 'required',
        'phone' => 'required',
    ]);
}

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return \App\User
 */
protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
        'address' => $data['address'],
        'phone' => $data['phone'],
        'role' => 2, // ý muốn là lưu user nếu hệ thông có role = 1 là admin, 2 là user :)))
    ]);
}

Đây là ví dụ mình muốn customize lại phần register, nhưng trong thực tế thì người dùng ko mong muốn phải nhập nhiều dữ liệu như này, chúng ta có thể cho người dùng đăng ký các trường mặc định như trong Laravel, có điều vào phần chức năng chỉnh sửa Profile thì chúng ta mới cho update cập nhật thông tin người dùng !!!

Truy suất người dùng đã xác thực

Khi các bạn đăng nhập thành công hệ thống thì các bạn có thể truy cập thông tin người dùng đã xác thực ở mọi nơi. Bạn cần use Illuminate\Support\Facades\Auth để sử nhá:

$id = Auth::user()->id; hoặc Auth::id() //Lấy về ID người .
$user = Auth::user() // Lấy về tất cả các thông tin của người dùng.
$email = Auth::user()->email // Lây về email người dùng.
...

Để kiểm tra người dùng đã xác thực hay chưa, chúng ta khai báo như sau:

if (Auth::check()) {
    echo "Nguoi dung da dang nhap he thong";
}

Middleware

Trong Laravel, authentication có middleware auth, hệ thống muốn chắc chắn rằng người dùng phải xác thực trước khi thực hiện thao tác trên hệ thống. Middleware nằm ở app/Http/Middleware/Authenticate.php. Các bạn có thể sử dụng trực tiếp trên routes hoặc trong hàm __construct() ở mỗi Controller

Route::get('profile', function () {
    // Only authenticated users may enter...
})->middleware('auth');
public function __construct()
{
    $this->middleware('auth');
}

Khi bạn gán dùng middleware auth trong route, bạn cũng có thể chỉ định guard nào sẽ được sử dụng để thực hiện công việc xác thực. Nhưng những guard mà bạn có thể lựa chọn chỉ được khai báo trong config/auth.php

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'token',
        'provider' => 'users',
    ],
],

Ví dụ

public function __construct()
{
    $this->middleware('auth:api');
}

Xác thực người dùng thủ công

Nhiều bạn không muốn dùng authentication controller có sẵn để xác thực người dùng. Đừng lo lắng về vấn đề đấy, chúng ta có thể tự xác thực theo ý muôn của chúng ta bằng cách sử dụng các class có sẵn ở Auth. Hãy chú ý import Illuminate\Support\Facades\Auth

<?php

namespace App\Http\Controllers;

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

class LoginController extends Controller
{
    /**
     * Handle an authentication attempt.
     *
     * @param  \Illuminate\Http\Request $request
     *
     * @return Response
     */
    public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) {
            // Authentication passed...
            return redirect()->intended('dashboard');
        }
    }
}

Bạn có thể theo dõi thêm ở video ở đây về xác thực thủ công để hiểu rõ hơn.

Chú ý
Ngoài cách sử dụng route('logout') để logout user ra ngoài khỏi hệ thống thì chúng ta có thể sử dụng Auth::logout()

Remember me

Khi mà người dùng request đăng nhập lên hệ thống thì trên server sẽ sinh ra session, đồng thời server sẽ set header trên response trả về client. Mục đích của header này là lưu cookie ở client. Trong cookie sẽ chứa session id tương ứng session trên server. Nhờ đó mà server sẽ biết được ai request ở lần tiếp theo.

Nhưng bạn biết đấy, Cookie thì lâu lâu thì thời gian expire của nó cũng hết, khi đó người dùng sẽ bị logout ra ngoài. Khi chúng ta tích vào ô check box Remember me khi đăng nhập thì Session ID của người dùng đó sẽ được băm ra rồi lưu trong bảng users tại trường remember_token, cái này sẽ duy trì đăng nhập lâu dài, user sẽ bị thoát ra ngoài hệ thống khi tự logout thủ công.

Tiếp nhé, tại sao user lại duy trì đăng nhập lâu dài được khi nhấn vào nút Remember me. Khi nhấn vào nút dó thì khi server set header thì sẽ set thêm expired date. Sau khi user tắt browse thì cookie này không bị mất. Mà khi Cookie còn hạn thì sẽ còn tự đăng nhập.

Code thủ công như sau:

public function authenticate(Request $request)
    {
        $credentials = $request->only('email', 'password');
        $remember = $request->filled('remember'); // true / false
        if (Auth::attempt($credentials, $remember)) {
            // Authentication passed...
            return redirect()->intended('dashboard');
        }
    }

Kết luận

Bài viết trên mình đã giới thiệu cho các bạn những thứ gì cơ bản nhất về authentication trong Laravel, còn rất nhiều hàm mà Auth hỗ trợ, các bạn có thể xem trong documentation của nó nhé.

Ở bài sau mình sẽ hướng dẫn các bạn clone ra những bộ khác nhau để tiện cho việc phân quyền.

Nếu gặp lỗi gì trong quá trình cài đặt hoặc kiến thức mình cung cấp không chính xác thì có thể liên hệ trực tiếp với minh qua các kênh thông tin phía cuối trang.

Nguồn tham khảo:

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!


Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Nguyễn Quang Đạt !
Comments
  TOC