12/08/2018, 13:41

Laravel - Token-Based Authentication

Introduction Authentication hay xác thực là quá trình thiết lập hoặc chứng thực một đối tượng nào đó là đáng tin cậy đối với ứng dụng hiện tại (thường sẽ là người dùng). Chúng ta thường nhầm lẫn khái niệm Authentication (xác thực) và Authorization (ủy quyền) - trao quyền cho người dùng ...

Introduction

Authentication hay xác thực là quá trình thiết lập hoặc chứng thực một đối tượng nào đó là đáng tin cậy đối với ứng dụng hiện tại (thường sẽ là người dùng). Chúng ta thường nhầm lẫn khái niệm Authentication (xác thực) và Authorization (ủy quyền) - trao quyền cho người dùng để thực thi các thao tác tương ứng với quyền đó trên hệ thống.

Như đã đề cập đến trong Laravel official documentation, việc triển khai authentication trong Laravel cho người mới bắt đầu là khá đơn giản - php artisan make:auth. Trong phiên bản 5.2, Multiple Authentication hay Multi Auth được giới thiệu cùng với đó là một số khái niệm khá mới như guards, providers, api guard. Hiện tại Laravel mặc định hỗ trợ hai loại authentication: session-based authenticationtoken-based authentication. Tuy nhiên chúng ta có thể tủy chỉnh hoặc tạo mới một guard để hỗ trợ một loại authentication nào đó khác ngoài hai loại trên. Sẽ có một số thay đổi và cải tiến cho authentication trong phiên bản 5.3 tuy nhiên phiên bản này vẫn chưa được phát hành chính thức do chưa hoàn hiện phần documentation. Trong bài viết này chúng ta sẽ đề cập đến token-based authentication trong Laravel cũng như cách sử dụng loại authentication này.

Token-Based Authentication Laravel

Trước khi tìm hiểu về cách sử dụng api guard trong Laravel hãy dành một chút thời gian để tìm hiểu thêm về Multiple authentication trong Laravel. Đầu tiên hãy tìm đến lớp có tên AuthManager và tìm đến phương thức guard trong lớp đó.

public function guard($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return isset($this->guards[$name])
                ? $this->guards[$name]
                : $this->guards[$name] = $this->resolve($name);
}

Phương thức này sẽ được kích hoạt khi ta sử dụng một số logic như Auth::check() hay Auth::guard('admin')->check(). Nếu chúng ta không chỉ định rõ loại guard được sử dụng, Laravel sẽ lấy giá trị mặc định defaults trong config/auth.php - web. Tiếp đó Laravel sẽ đọc từ file config/auth.php và tạo các driverprovider tương ứng. Cụ thể, phương thức resolve sẽ được gọi, trong phương thức này bằng việc kết hợp thông tin từ file configuration, phương thức createSessionDriver hoặc createTokenDriver sẽ được gọi để tạo các driver tương ứng. Kết quả khi ta đặt giá trị của driver là session chúng ta sẽ có một instance của SessionGuard class và TokenGuard nếu driver được đặt là token.

Bên trong constructor của lớp TokenGuard chúng ta sẽ thấy hai property inputKey và storageKey với giá trị được đặt là api_token. Hiểu một cách đơn giản, inputKey là giá trị được truyền thông qua request - ?api_token=some-random-string, trong khi đó storageKey sẽ là tên của trường trong cơ sở dữ liệu được dùng để tìm đến người dùng tương ứng. Giá trị của storageKey sẽ được dùng làm đối số cho phương thức retrieveByCredentials để tìm đến người dùng tương ứng với token trong request. Phương thức retrieveByCredentials sẽ được gọi theo provider tương ứng, driver mặc định là eloquent nên chúng ta có thể tìm đến lớp EloquentUserProvider để hiều rõ hơn về phương thức này. Như những điều đã đề cập ở trên, chúng ta cần tạo một trường để lưu thông tin của token trong bảng users (mặc định Laravel cung cấp một lớp migration cho model User), ví dụ:

$table->string('api_token', 65)->unique();

Mặc dù logic cho việc tìm đến người dùng bằng api_token là khá phức tạp tuy nhiên kết quả cuối cùng sẽ là một câu truy vấn khá đơn giản: User::where('api_token', $token)->first().

Cách sử dụng Token-Based Authentication

Giả sử chúng ta đang xây dựng API cho ứng dụng di động và chúng ta muốn tạo một endpoint để lấy thông tin của một người dùng nào đó. Bước đầu tiên là tạo một route để nhận các API call. Để đơn giản cho việc minh họa, chúng ta sẽ sử dụng closure thay vì controller method trong trường hợp này.

Route::group(['prefix' => 'api'], function () {
    Route::get('users/{user}', function (AppUser $user) {
        return $user;
    })->name('api.me');
});

Trong ví dụ trên, chúng ta đã tạo một route khá đơn giản api/users/{user} để lấy thông tin về người dùng. Laravel hỗ trợ Implicit Route Model Binding nên key user sẽ được tự động liên kết đến User model trong trường hợp này. Bên trong closure chúng ta đơn giản trả về user tìm được dưới dạng JSON. Trong ví dụ nói trên bất kỳ ai cũng có thể tạo request đến endpoint đó và lấy về thông tin người dùng, tuy nhiên đó không phải là những gì sẽ được làm trên thực tế. Giả sử chúng ta muốn giới hạn endpoint trên cho những người dùng đã được xác thực. Điều này có thể thực hiện đơn giản bằng cách sử dụng middleware:

Route::group(['prefix' => 'api', 'middleware' => 'auth'], function () {
    //
});

Ở đây chúng ta đã sử dụng auth middleware cung cấp mặc định bởi framework và phương thức handle của middleware đó có nội dung như sau:

public function handle($request, Closure $next, $guard = null)
{
    if (Auth::guard($guard)->guest()) {
        if ($request->ajax() || $request->wantsJson()) {
            return response('Unauthorized.', 401);
        }

        return redirect()->guest('login');
    }

    return $next($request);
}

Để ý rằng chúng ta có một parameter thứ ba $guard với giá trị mặc định là null và bên trong phương thức handle chúng ta có một logic để kiểm tra xem người dùng đã được xác thực hay chưa Auth::guard($guard)->guest(). Nếu chúng ta gọi middleware này với giá trị guard mặc định (web) chúng ta sẽ quay trở lại cách xác thực truyền thống. Tuy nhiên trong trường hợp này chúng ta muốn sử dụng token-based thay vì session-based authentication, vì vậy chúng ta sẽ chỉ định sử dụng api guard cho các API endpoint của chúng ta. Điều này có thể được thực hiện bằng các sử dụng Middleware Parameters:

Route::group(['prefix' => 'api', 'middleware' => ['api', 'auth:api']], function () {
    //
});

Sau thay đổi này, các request đến enpoint nói trên phải cung cấp một trường có tên api_token, nếu không request đó sẽ được coi là không hợp lệ, ví dụ: http://foo.com/api/users/1?api_token=9osToozuZeDIQsOWKaHy4lj5N6Zo91I7OJtad8kdorQoUTKWmRkdGI4SibXoisRcT

Conclusion

Trong bài viết này, mình đã giới thiệu một cách khá tổng quan về authentication trong Laravel nói chung và token-based authentication nói riêng. Có khá nhiều thay đổi trong phiên bản 5.3 sắp tới, trong đó có thay đổi về routes (chuyển thành first-class citizen và sẽ có hai file riêng biệt cho web routes và api routes) và Laravel Passport (full implementation of OAuth 2 server) là hai điều mình cảm thấy thú vị nhất. Còn rất nhiều thay đổi khác đã được đề cập đến trong Laracon, chúng ta sẽ cùng chờ đợi và trải nghiệm chúng trong phiển bản chính thức sắp tới.

0