Multiple authentication in Laravel 5.2

Vâng, shitpost mãi cũng chán rồi, dạo này trời nóng cũng chả có cái vẹo gì vui vẻ trong đầu được. Thôi hôm nay viết một bài-tạm-coi-là-tử-tế vậy

Multiple authentication in Laravel 5.2

Đây là một tính năng mới, được đưa vào từ bản Laravel 5.2. Nếu như trong các phiên bản 5.1 trở về trước , facade Auth hoạt động chủ yếu dựa trên mô hình của lớp xác thực authentication layer trong các web-app truyền thống : Thông tin xác thực ( thường là username / email, password ) được truyền tới controller, tại đây thực hiện việc kiểm tra xem các thông tin này có hợp lệ hay không, và thực hiện việc redirect. Đồng thời, nếu thông tin xác thực là hợp lệ thì dữ liệu của user đó sẽ được lưu lại trong sesssion để tiếp tục sử dụng. Về cơ bản thì xử lí cho web app như thế là khá ổn, nhưng nếu giả sử ứng dụng của bạn muốn sử dụng các cơ chế xác thực stateless, non-session thì sao, chẳng hạn một hệ thống API xác thực qua các JSON web tokens chẳng hạn. Hay nếu trong ứng dụng của bạn, muốn có đồng thời cả 2 hay nhiều hơn cơ chế xác thực như trên thì sao ? Để làm được điều này ở các bản Laravel 5.1 trở về trước là hết sức khó khăn, nhưng Laravel 5.2 thì khác. Trong bài viết này, chúng ta sẽ cùng tìm hiểu một chút về tính năng mới trong bản 5.2 này.

Let's dive into the rabbit hole called config/auth.php

Về cơ bản thì hầu hết các thiết lập liên quan đến Authentication trong laravel sẽ được khai báo trong file config/auth.php, vậy ta hãy cùng nhau dùng file này làm đầu mối điều tra đầu tiên. Nội dung của nó có lẽ sẽ tương tự thế này

<?php
return [

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

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

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => Benlly\Entities\User::class,
        ],
        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'email' => 'auth.emails.password',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],
];

Đọc qua ta sẽ thấy ở đây có 2 khái niệm tương đối lạ, cần lưu ý là guardsproviders. Về mặt định nghĩa mà nói thì :

  • guards sẽ định nghĩa cách mà user được xác thực đối với từng request.
  • providers lo định nghĩa cách mà thông tin về user được lấy ra khỏi nơi lưu trữ.

Nghe khá là rối rắm, khó hiểu phải không. Tin vui là laravel đã cung cấp sẵn 2 loại guardtokensession, cũng như 2 loại providereloquentdatabase . Các khái niệm định nghĩa sẵn này khá mạnh, và có thể nói là bao quát hết 90% các ứng dụng mà bạn định viết rồi, nên thường là bạn cũng không phải đi sâu vào tìm hiểu làm gì, học cách dùng thôi là đủ. Còn tin buồn là, 10% trường hợp còn lại, bạn sẽ phải tự viết. Laravel 5.2 cũng cho phép bạn hoàn toàn có thể tự định nghĩa các loại guard hay provider của riêng mình, nhưng việc này sẽ khá là khoai đấy, ít ra là với dám dev cùi cùi chúng ta .

Quay trở lại với file config/auth.php của ta, ta sẽ thấy.

  • 'defauls' : Là nơi khai báo các thiết lập mặc định. Khai ta gọi facade Auth giống như ở các bản cũ, Auth::user() chẳng hạn, sẽ sử dụng các guard và provider cung cấp ở đây.

  • 'guards : Các thiết lập về guard được định nghĩa ở đây. Như đã nói phía trên, laravel cung cấp cho ta 2 guard driver viết sẵn là sessiontoken. session không xa lạ gì, chính là cơ chế xác thực như ở các phiên bản cũ : Thông tin xác thực được gửi lên và thực hiện việc kiểm tra, nếu thành công sẽ lưu thông tin user lấy được vào trong session để sử dụng. token là cơ chế xác thực thứ hai, trong đó khác biệt lớn nhất là nó state-less. Việc thực hiện hành động xác thục sẽ xảy ra với từng request. Để hiểu cụ thể hơn xem mỗi loại guard driver này cung cấp những phương thức nào, cũng như cách thức hoạt động cụ thể, ta có thể xem source code tại Illuminate\Auth\SessionGuardIlluminate\Auth\TokenGuard.

  • 'providers' : Tương tự như với guard, Laravel 5.2 cũng định nghĩa sẵn cho ta 2 loại driver thông dụng là eloquentdatabase. Tên gọi cũng khá dễ hiểu, với driver eloquent, đối tượng được xử lí sẽ là các Eloquent Model, còn với driver database, dữ liệu sẽ được truy vấn trực tiếp từ các bảng trong database. Giống như với guard, để hiểu các thức hoạt động cụ thể, cũng như các phương thức được cung cấp, ta có thể tham khảo source code tại Illuminate\Auth\DatabaseUserProviderIlluminate\Auth\EloquentUserProvider.

Using multiple authentication in our app

Tìm hiểu qua về các khái niệm là thế, giờ hãy thử đặt ra một tình huống cụ thể và thường gặp : Giả sử app của chúng ta bây giờ, có 2 đối tượng là User và Admin riêng biệt. User đăng nhập bằng email và password, có thể reset password trong trường hợp chẳng may quên, còn admin sẽ đăng nhập bằng username và password, không có reset. Để thực hiện việc này ở Laravel 5.1 trở về trước sẽ tốn không ít công sức, còn trong Laravel 5.2 thì sao. Trong phạm vi bài viết này, xin bỏ qua các công đoạn tạo table, tạo model ... blah blah. Đoạn này mời các bạn dùng trí tưởng tượng của bản thân tự bổ sung vậy nhé. Việc đầu tiên chúng ta cần làm, khai báo lại file config/auth.php về dạng :

<?php

return [

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

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

        // For admin
        'admins' => [
            'driver'   => 'session',
            'provider' => 'admins'
        ]
    ],

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

        // For admin
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Admin::class
        ]
    ],

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'email' => 'auth.emails.password',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ],

]

Đơn giản như vầy thôi. Ta dùng xác thực với User là chính, nên khai báo defaults guard là users .Về cơ chế xác thực của user và admin cơ bản là như nhau, nên ta khai báo cho cả hai dùng guard driver session. Đối tượng dùng để xác thực ở đây lần lượt là 2 EloquentModel UserAdmin , nên ta lần lượt khai báo 2 user provider tương ứng, cùng dùng provider driver eloquent, nhưng sử dụng model khác nhau. Admin không có reset password, nên không có khai báo tương ứng trong phần passwords . Xong bước thứ nhất.

Tiếp đến là chỉnh sửa lại file Model. Để áp ụng được Auth facade, thì cả 2 model UserAdmin, ta phải cho extends Illuminate\Foundation\Auth\User. Ngoài ra, đối với guard không phải là default (ở đây là admins) , trong file model, ta cần khai báo thêm biến

protected $guard = "admins";

Về cơ bản thì công việc khai báo đến đây là hoàn thành, tiếp tới là sử dụng. Vì đã khai báo default, nên nếu ta định sử dụng xác thực với đối tượng là User, ta có thể gọi giống như ở các bản Laravel trước

auth()->attempt(['email' => '', 'password' => '']);
Auth::attempt(['email' => '', 'password' => ''])

Trong trườnghợp sử dụng đối tượng xác thực là admin, hoặc bạn muốn viết một cách rõ ràng hơn, khi gọi facade Auth, ta cần chỉ rõ guard mình muốn sử dụng

Auth::guard('admins')->attempt(['username' => '', 'password' => '']);
Auth::guard('web')->attempt(['username' => '', 'password' => '']);

Tất nhiên, đây là trong trường hợp lí tưởng, khi ta có thể sử dụng các driver mà laravel đã cung cấp sẵn. Nhưng đôi khi, ta cần phải tùy biến nhiều hơn thế thì sao. Chẳng hạn việc xác thực không chỉ đơn thuần là kiểm tra username password có khớp, hay token có hợp lệ, mà ta cần thêm một bước verify khác nữa , hay sau khi xác thực, thông tin mà ta muốn lưu vào session không chỉ nằm trong User mà còn gồm nhiều trường ở các bảng khác nữa .... Trong các tình huống đó, có lẽ việc bạn cần là tự viết những custom guard / provider của riêng mình . Nội dung này sẽ được giới thiệu trong bài viết tháng tới. Chào thân ái và quyết thắng.