Laravel Passport

Laravel Passport

Lưu ý - một số thông tin về Laravel Passport package trong bài viết này có thể đã outdated, bạn nên kiểm tra trên documentation của Laravel để có được thông tin chính xác nhất 😊

Tổng quan về OAuth 2.0

OAuth 2.0 là một authorization framework cho phép các ứng dụng bên thứ ba có quyền truy cập hạn chế đến một dịch vụ HTTP. Trong mô hình client-server truyền thống, client sẽ xác thực với server sử dụng thông tin của chủ sở hữu tài nguyên (resource owner) với mục đích truy cập các tài nguyên đã được bảo vệ hoặc hạn chế truy cập (protected resource). Trong mô hình này, resource owner sẽ chia sẻ thông tin của mình cho các bên thứ ba (third party) với mục đích cho phép ứng dụng của các bên thứ ba truy cập vào các tài nguyên đã được bảo vệ. Việc làm trên có khá nhiều hạn chế. OAuth khắc phục những hạn chế của phương pháp trên bằng việc giới thiệu authorization layer và tách biệt vai trò của client ra khỏi resource owner. Trong OAuth client truy cập các tài nguyên đã được bảo vệ dưới sự giám sát của resource owner, tài nguyên sẽ được lưu trữ tại resource server. Thay vì sử dụng thông tin của resource owner, client sẽ sử dụng access token (là một chuỗi mã hóa các thuộc tính truy cập). Access token được cung cấp cho client thông qua authorization server cùng với sự chấp thuận của resource owner. Client sau đó có thể sử dụng access token để truy cập các tài nguyên đã được bảo vệ trên resource server.

Các roles (actors) trong OAuth 2.0

  • resource owner : là một thực thể có khả năng cấp quyền truy cập cho các tài nguyên đã được bảo vệ, khi resource owner là một người cụ thể, chúng ta thường gọi là end-user.
  • resource server : là nơi lưu trữ các tài nguyên đã được bảo vệ, có chức năng tiếp nhận và xử lý các yêu cầu truy cập sử dụng access token.
  • client : là các ứng dụng bên thứ ba, các ứng dụng này sẽ gửi các yêu cầu truy cập protected resource dưới sự chấp thuận của resource owner.
  • authorization server : server này có chức năng cung cấp access token cho client sau khi client đã xác thực với resource owner và nhận được sự ủy quyền từ phía resource owner.

Các khái niệm cơ bản trong OAuth 2.0

Access Token

Hiểu một cách đơn giản access token giống như một chiếc chìa khóa sử dụng để truy cập đến các protected resource. Access token là một chuỗi biều diễn sự ủy quyền cho client và thường trong suốt đối với client. Các token thường mang trong mình các scope (permission) và thời gian truy cập được cấp bởi resource owner và hiện thực hóa bởi resource serverauthorization server. Access token cung cấp một lớp trừu tượng thay thế cho cách truyền thống (username và password). Access token sẽ được hiểu bởi resource serverresource server không yêu cầu phải hiểu được nhiều phương thức xác thực khác nhau. Access token có thể được mã hóa bằng nhiều cách khác nhau, có các định dạng và cấu trúc khác nhau.

Refresh Token

Refresh token được sử dụng để yêu cầu một access token mới. Refresh token được cung cấp cho client bởi authorization server. Loại token này được sử dụng khi access token đã hết hạn hoặc để tạo access token mới với số lượng scope bằng hoặc ít hơn access token cũ. Refresh token chỉ được hiểu bởi authorization server và không được cung cấp cho resource server.

Access Token Scope

Client có thể chỉ định scope cho yêu cầu truy cập bằng việc sử dụng scope request parameter. Authorization server sẽ sử dụng giá trị của scope parameter để thông báo cho client các scopeaccess token có.

OAuth 2.0 cung cấp khá nhiều phương pháp cho phép client thu được access token, các phương pháp đó còn được gọi là grant.

Authorization Code Grant

Grant này khá phổ biến và được sử dụng rộng trong các ứng dụng web hiện đại. Grant này bao gồm hai bước cơ bản:

Obtaining authorization code

Trong bước này client sẽ được chuyển tiếp đến authorization server với các parameter sau trong request:

  • respone_type: với giá trị là code
  • client_id: định danh của client
  • redirect_uri: nơi mà resource owner sẽ được authorization server chuyển tiếp về phía client sau khi quá trình giao tiếp giữu authorization server và resource owner hoàn tất. Parameter này không bắt buộc phải có trong request.
  • scope: danh sách các quyền (permissions)
  • state: thông thường sẽ là CSRF token.

Tất cả các parameter trên sẽ được kiểm chứng bởi authorization server. Nếu như resource owner chấp thuận yêu cầu, resource owner sẽ được chuyển tiếp từ authorization server về phía client với những thông tin sau:

  • code: authorization code
  • state: giá trị của state trong request nói trên, giá trị này sẽ được sử dụng để kiểm tra authorization code được trả về cho client thực hiện request nói trên.

Acquiring Access Token

Sau khi đã nhận được authorization code, client sẽ tạo một POST request đến authorization server với các parameter sau:

  • respone_type: với giá trị là authorization_code
  • client_id: định danh của client
  • client_secret: client secret
  • redirect_uri: nơi mà resource owner sẽ được authorization server chuyển tiếp về phía client sau khi quá trình giao tiếp giữu authorization server và resource owner hoàn tất. Parameter này không bắt buộc phải có trong request.
  • code: authorization code thu được từ bước 1.

Authorization server sẽ trả về các thông tin sau:

  • token_type: kiểu của access token (ví dụ: Bearer)
  • expires_in: thời gian sống của access token (TTL / Time-To-Live)
  • access_token: giá trị của access token
  • refresh_token: giá trị của refresh_token, token này được sử dụng để tạo access token mới khi access token cũ đã hết hạn (expired)

Implicit Grant

Implicit Grant khá giống so với Authorization Grant tuy nhiên có hai điểm khác biệt chính là: giá trị client_secret sẽ không được sử dụng, và authorization code sẽ không được tạo ra thay vào đó sẽ là giá trị của access token

Cụ thể, client sẽ chuyển tiếp resource owner đến authorization server với các parameter sau:

  • respone_type: với giá trị là token
  • client_id: định danh của client
  • redirect_uri: nơi mà resource owner sẽ được authorization server chuyển tiếp về phía client sau khi quá trình giao tiếp giữu authorization server và resource owner hoàn tất. Parameter này không bắt buộc phải có trong request.
  • scope: danh sách các quyền (permissions)
  • state: thông thường sẽ là CSRF token.

Tất cả các parameter trên sẽ được kiểm chứng bởi authorization server. Nếu như resource owner chấp thuận yêu cầu, resource owner sẽ được chuyển tiếp từ authorization server về phía client với những thông tin sau:

  • token_type: kiểu của access token (ví dụ: Bearer)
  • expires_in: thời gian sống của access token (TTL / Time-To-Live)
  • access_token: giá trị của access token
  • state: giá trị của state trong request nói trên, giá trị này sẽ được sử dụng để kiểm tra authorization code được trả về cho client thực hiện request nói trên.

Resource Owner Pasword Credentials Grant (Password Grant)

Pasword Grant được sử dụng khi mối quan hệ giữa clientresource owner là đáng tin cậy và gần gũi. Trong loại grant này, client sẽ yêu cầu thông tin từ phía resource owner bao gồm usernamepassword; client sau đó sẽ gửi request lên authorization server với các parameter sau:

  • grant_type: với giá trị là password
  • client_id: định danh của client
  • client_secret: giá trị secret của client
  • scope: danh sách các quyền (permissions)
  • username: username của resource owner
  • password: password của resource owner

Authorization server sẽ trả về những thông tin sau nếu như các parameter trên là chính xác:

  • token_type: kiểu của access token (ví dụ: Bearer)
  • expires_in: thời gian sống của access token (TTL / Time-To-Live)
  • access_token: giá trị của access token
  • refresh_token: giá trị của refresh_token, token này được sử dụng để tạo access token mới khi access token cũ đã hết hạn (expired)

Client Credentials Grant

Đây là dạng grant đơn giản nhất của OAuth 2.0 được sử dụng khi việc truy cập protected resource không yêu cầu permissions từ resource owner

Client sẽ gửi request đến authorization server với các parameter sau:

  • grant_type: với giá trị là client_credentials
  • client_id: định danh của client
  • client_secret: giá trị secret của client
  • scope: danh sách các quyền (permissions)

Authorization server sẽ trả về những thôn tin sau nếu như các parameter trên là chính xác:

  • token_type: kiểu của access token (ví dụ: Bearer)
  • expires_in: thời gian sống của access token (TTL / Time-To-Live)
  • access_token: giá trị của access token

Refresh Token Grant

Refresh token grant được sử dụng để yêu cầu access token mới khi access token đã hết hạn mà không phải đi qua tất cả các bước nhưng trong các grant mà giá trị trả về có chứa refresh token

Client sẽ gửi request đến authorization server với các parameter sau:

  • grant_type: với giá trị là refresh_token
  • refresh_token: giá trị của refresh token
  • client_id: định danh của client
  • client_secret: giá trị secret của client
  • scope: danh sách các quyền (permissions)

Authorization server sẽ trả về những thôn tin sau nếu như các parameter trên là chính xác:

  • token_type: kiểu của access token (ví dụ: Bearer)
  • expires_in: thời gian sống của access token (TTL / Time-To-Live)
  • access_token: giá trị của access token
  • refresh_token: giá trị của refresh_token, token này được sử dụng để tạo access token mới khi access token cũ đã hết hạn (expired)

Nên sử dụng loại grant nào?

Sơ đồ dưới đây cho ta thấy các use-case cho từng loại grant đã trình bày ở trên:

Grant Usage Source: http://alexbilbie.com/

Laravel Passport

Giới thiệu chung

Laravel Passport là một package tùy chọn được viết bởi Taylor Otwell - the creator of Laravel và tương thích với phiên bản Laravel 5.3 vừa được ra mắt chính thức trong Laracon EU. Laravel Passport giúp cho việc sử dụng OAuth 2.0 với Laravel trở nên dễ dàng hơn. Về cơ bản Laravel Passport được xây dựng trên nền của OAuth 2.0 Server viết bởi Alex Bilbie được được liệt kê trong danh sách các package của The League of Extraordinary Packages, tuy nhiên nó cung cấp một interface thuận tiện hơn khi làm việc với Laravel cũng như bổ sung thêm một số tính năng mới so với package gốc. Trước khi Laravel Passport ra mắt, để làm việc với OAuth 2.0 trong Laravel, chúng ta có thể sử dụng Laravel OAuth2 Server package được viết bởi Luca Degasperi; package này cung cấp khá đầy đủ những chức năng cần thiết.

Cài đặt và cấu hình

Cài đặt

Laravel Passport yêu cầu phiên bản Laravel framework tối thiểu là 5.3 và có thể được cài đặt thông qua Composer

composer require laravel/passport

Giống như hầu hết các package cho Laravel khác, chúng ta cần thêm service provider của Laravel Passport trong file config/app.php (việc thêm service provider cần được thực hiện sau khi package đã được tải về thông qua Composer)

Laravel\Passport\PassportServiceProvider::class,

Laravel Passport cung cấp sẵn một số migration class để tạo các bảng cần thiết để lưu trữ authorization codes, access tokens, refresh tokens, personal access tokens, thông tin về clients (danh sách các file migration có thể xem tại đây). Để cài đặt các bảng cần thiết cho Laravel Passport, chúng ta dùng lệnh sau:

php artisan migrate

Bước tiếp theo, chúng ta cần tạo encryption keys (được dùng khi tạo access tokens) và tạo các client liên quan đến Personal Access GrantPassword Grant

php artisan passport:install

Về cơ bản, command trên sẽ gọi đến hai command nhỏ hơn là: KeysCommandClientCommand

  • KeysCommand : command này có nhiệm vụ tạo các RSA private (oauth-private.key) /public (oauth-public.key) keys được sử dụng để mã hóa các token (ví dụ như JWT) sử dụng giải thuật RSA.
  • ClientCommand : command này được sử dụng để tạo các client liên quan đến các loại grant khác nhau (auth code client, password client và personal client) - chi tiết về command có thể xem tại đây

Tương tự như khi sử dụng Authentication scaffoding trong Laravel, để đăng ký tất cả những route liên quan đến authentication chúng ta có thể sử dụng Route::auth(). Laravel Passport cũng cung cấp một dòng lệnh khá đơn giản để đăng ký một số API endpoint liên quan đến OAuth. Chúng ta có thể thực hiện việc trên bằng cách thêm câu lệnh Passport::routes() vào trên trong phương thức boot() của app/Providers/AuthServiceProvider cung cấp sẵn bởi Laravel (để xem những route đã được đăng ký bởi Laravel Passport chúng ta có thể tham khảo tại đây)

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
...

class AuthServiceProvider extends ServiceProvider
{
    ...

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
    }
}

Bước cuối cùng là chuyển api driver từ token sang passport trong file config/auth.php. Mặc định Laravel cung cấp một chức năng khá đơn giản nhưng hữu ích - Token-based authentication (authentication sử dụng apiKey here), tuy chúng ta cần thay đổi driver mặc định này sang passport nếu muốn sử dụng OAuth 2.0 để authorize các API requests.

...
'guards' => [
    ...

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],
...

Cấu hình

Trong bước đầu tiên, chúng ta cần thêm một trait - Laravel\Passport\HasApiTokens cho authentication model - mặc định sẽ là App\User. HasApiTokens trait cung cấp một số phương thức trợ giúp trong việc lấy thông tin liên quan đến token của user, kiểm tra scope (permission) hoặc tạo mới personal access token.

<?php

namespace App;

use Laravel\Passport\HasApiTokens;
...

class User extends Authenticatable
{
    use HasApiTokens;
}

Tiếp theo chúng ta sẽ cấu hình thời gian sống cho refresh tokens và access tokens, việc này có thể thực hiện trong AuthServiceProvider. Mặc định những token này sẽ có thời gian sống khá dài (100 years !!!) và access token sẽ không cần phải được refreshing. Dưới đây là một ví dụ lấy ra từ Laravel official document:

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
...

class AuthServiceProvider extends ServiceProvider
{
    ...

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
        Passport::tokensExpireIn(Carbon::now()->addDays(15));
        Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
    }

Đôi khi việc lưu trữ các access token đã hết hạn là không cần thiết, nhất là khi access token có thời gian sống ngắn thì sau một khoảng thời gian database của chúng ta sẽ chứa khá nhiều dữ liệu không cần thiết. Laravel Passport cung cấp một phương thức giúp việc loại bỏ các tokens trên trở nên dễ dàng hơn:

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
...

class AuthServiceProvider extends ServiceProvider
{
    ...

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::routes();
        Passport::tokensExpireIn(Carbon::now()->addDays(15));
        Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
        Passport::pruneRevokedTokens();
    }

Supported Grant Types

Laravel Passport hỗ trợ bốn loại grant: Authorization Code Grant, Refresh Token Grant, Password Grant và Personal Access Grant. Trong đó ba loại grant đầu được hỗ trợ sẵn trong The PHP League - OAuth 2.0 Server package. Personal Access Grant được cung cấp riêng bởi Laravel Passport, chúng ta thường thấy loại grant này trong một số service như GitHub - Personal Access Tokens (để xem những grant được hỗ trợ bởi Laravel Passport, chúng ta có thể tìm xem trong service provider của Laravel Passport)

Authorization Code Grant (link)

Loại grant này được sử dụng khá phổ biến trong các ứng dụng web hiện tại và thường nhận được sự quan tâm nhiều nhất từ cộng đồng developer. Trong Laravel Passport, việc sử dụng loại grant này được thực hiện qua một số bước như sau:

  • Tạo mới client: Việc tạo mới client trong Laravel Passport được thực hiện quan một Artisan console command. Sau khí quá trình thực hiện command kết thúc, một client mới sẽ được tạo mới một số attribute như: name, secretredirect.
php artisan passport:client
  • Request authorzation code: Sau khi client đã được tạo, client ID và client secret sẽ được sử dụng để request authorization code và access token. Việc request authorization code sẽ được thực hiện bằng cách redirect người dùng đến /oauth/authorize route cùng với các parameter: response_type (code), client_id, redirect_uriscope. Nếu người dùng chấp thuận request nói trên, người dùng sẽ được redirect từ authroization server về redirect_uri đã cung cấp của client với các giá trị: codestate. Sau đây là ví dụ từ Laravel official documentation:
Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => '',
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});
  • Request access token using authroization code: Sau khi đã có được authorization code, client sẽ thực hiện một POST request đến /oauth/token route với authorization code đã có ở bước trên để lấy về access token. Sau đây là ví dụ về request trên từ Laravel official documentation:
$http = new GuzzleHttp\Client;

    $response = $http->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
            'redirect_uri' => 'http://example.com/callback',
            'code' => $request->code,
        ],
    ]);

Refresh Token Grant (link)

Trong trường hợp access token có thời gian sống ngắn, chúng ta có thể sử dụng refresh token để tạo mới một access token. Với Laravel Passport, việc trên có thể thực hiện bằng việc tạo một POST request đến route /oauth/token với các parameters: grant_type (refresh_token), client_id, client_secret, và scope. Dưới đây là ví dụ được lấy ra từ Laravel official documentation:

$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'refresh_token',
        'refresh_token' => 'the-refresh-token',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'scope' => '',
    ],
]);

return json_decode((string) $response->getBody(), true);

Kết quả trả về sẽ chứa các thông tin về token_type, expires_in (thời gian sống cho access token), access_tokenrefresh_token.

Password Grant (link)

Như đã trình bày ở trên, loại grant này được sử dụng khi mối quan hệ giữa resource ownerclient là khá gần gũi và tin tưởng, loại grant này thường được sử dụng khi xây dựng API cho các ứng dụng di động. Laravel Passport cung cấp hỗ trợ cho loại grant này muộn hơn so với các loại grant khác. Tương tự như Authorization Code Grant, chúng ta cần tạo client khi sử dụng loại grant này. Việc tạo mới khá tương tự, tuy nhiên chúng ta cần thêm --password flag cho client command

php artisan passport:client --password

Việc request access token cũng được thực hiện bằng việc gửi POST request đến /oauth/token route với các parameters sau: grant_type (password), client_id, client_secret, username, passwordscope. Nếu request được thực hiện thành công, kết quả trả về sẽ bao gồm: token_type, expires_in (thời gian sống cho access token), access_tokenrefresh_token. Dưới đây là ví dụ từ Laravel Official Documentation:

$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => '[email protected]',
        'password' => 'my-password',
        'scope' => '',
    ],
]);

return json_decode((string) $response->getBody(), true);

Personal Access Grant

Loại grant này cung cấp một cách đơn giản hơn để tạo mới access token mà không cần phải qua các bước như trong Authorization Code Grant. Người dùng sẽ tạo personal access token qua giao diện ứng dụng và sử dụng chúng cho việc testing API của các ứng dụng bên thứ ba. Personal access token luôn có thời gian sống khá dài và thờ gian sống đó sẽ không bị thay đổi khi sử dụng Passport::tokensExpireInPassport::refreshTokensExpireIn Việc tạo mới client cho loại grant này cũng tương tự như Password Grant, tuy nhiên thay vì sử dụng --password flag chúng ta sẽ sử dụng --personal flag

php artisan passport:client --personal

Việc tạo mới personal access token có thể thực hiện bằng việc sử dụng createToken trong Larravel\Passport\HasApiTokens (link)

$token = $user->createToken('Token Name')->accessToken;
$token = $user->createToken('Token Name', ['place-orders'])->accessToken;

Laravel Passport cung cấp một số API endpoint giúp cho việc sử dụng và quản lý personal access token trở nên dễ dàng hơn:

  • GET /oauth/scopes: lấy ra tất cả các scope đã được định nghĩa và có thể gán cho personal access token.

  • GET /oauth/personal-access-tokens: lấy tra danh sách các personal access token của authenticated user.

  • POST /oauth/personal-access-tokens: tạo mới personal access token cho người dùng (parameter gồm namescopes)

  • DELETE /oauth/personal-access-tokens/{token-id}: xóa bỏ personal access token sử dụng ID của token đó.

Route Protection

Laravel Passport sử dụng middleware để restrict access đến các route chỉ có quyền truy cập khi có access token. Các request không có access token hoặc access token sai hoặc hết hạn sẽ được loại bỏ và unauthenticated response sẽ được trả về.

Route::get('/user', function () {
    // API endpoints...
})->middleware('auth:api');

Scopes

Laravel Passport cho phép tạo mới các scope hay permisssion bằng việc sử dụng phương thức tokensCan trong AuthServiceProvider. Đối số cho phương thức trên sẽ là một mảng chứa tên của scope và mô tả cho scope tương ứng.

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
...

class AuthServiceProvider extends ServiceProvider
{
    ...

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        Passport::tokensCan([
		    'scope-1' => 'Description...',
		    'scope-2' => 'Description...',
		]);
    }

Việc sử dụng scope có thể được thực hiện bằng việc định nghĩ parameter có tên scope với giá trị danh sách các scope phân cách nhau bởi dấu cách khi request access token trong authorization cod grant. Trong trường hợp Personal Access Grant, danh sách scope sẽ là một mảng được truyền qua tham số thứ hai của phương thức createToken.

Laravel Passport cung cấp một số phương pháp để kiểm tra scope sử dụng middleware, các middleware này sẽ được đăng ký bên trong app/Http/Kernel.php class tại $routeMiddleware property.

protected $routeMiddleware = [
	...
	'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
	'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
	...
];
  • Kiểm tra sự hiện diện của tất cả các scope trong danh sách
Route::get('/me', '[email protected]')->middleware('scopes:scope-1,scope-2');
  • Kiểm tra sự hiện diện của một trong những scope trong danh sách
Route::get('/me', '[email protected]')->middleware('scope:scope-1,scope-2');

Conlusion

Trong bài viết này, mình đã trình bày một cách khá tổng quan về OAuth 2.0 nói chung và Laravel Passport nói riêng. Laravel Passport là một trong những thay đổi thú vị nhất trong phiên bản mới nhất của Laravel - 5.3, nó giúp cho việc integrate OAuth 2.0 với Laravel trở nên dễ dàng hơn và nhanh chóng hơn.

References