Authenticate với Laravel Passport

LAMP Nov 05, 2021

 Trong quá trình phát triển các API, rất cần thiết phải bảo vệ dữ liệu vì đây là tài nguyên qúy gía nhất phục vụ cho sự phát triển của trang web. Với API, Laravel hỗ trợ một package là Passport giúp thực hiện xác thực trong API đơn giản hơn, nó cung cấp đầy đủ OAuth2. Laravel Passport được xây dựng dựa trên League OAuth2 server được phát triển bởi Alex Bilbie. Trước tiên để thực hành dùng Laravel Passport, ta phải hiểu về OAuth2 trước đã nhé 😊

1. 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 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.

Roles trong OAuth 2.0

  • resource owner : 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ể, 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 server và authorization 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 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 scope mà access 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 rãi. 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
  • 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
  • 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

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
  • 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
  • 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)

Password Grant được sử dụng khi mối quan hệ giữa client và resource owner là đáng tin cậy. Trong loại grant này, client sẽ yêu cầu thông tin từ phía resource owner bao gồm username và password; 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
  • 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
  • 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

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

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
  • 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

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
  • 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

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

2. Sử dụng Laravel Passport

Generate Key

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 Grant và Password Grant

php artisan passport:install

sau khi chạy xong thêm Laravel\Passport\HasApiTokens vào model 'App\user'. Trait này sẽ cung cấp 1 vài hepler cho phép kiểm tra token và phạm vi của người dùng.

Passport Config

<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
    use Notifiable, HasApiTokens;
}

Tiếp theo là gọi phương thức Passport::routes trong AuthServiceProvider, Phương thức này dùng để đăng ký những routes cần thiết để cho những vấn đề liên quan đến access tokens and revoke access tokens, clients, and personal access tokens

<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];
    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
        Passport::routes();
    }
}

Cuối cùng là tại file config/auth.php, chọn driver của api là passport, nó sẽ điều hướng ứng dụng sử dụng Passport's TokenGuard khi các thực yêu của của API

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

Create Api Routes

<?php
use Illuminate\Http\Request;
Route::group([
    'prefix' => 'auth'
], function () {
    Route::post('login', 'AuthController@login');
    Route::post('signup', 'AuthController@signup');
  
    Route::group([
      'middleware' => 'auth:api'
    ], function() {
        Route::get('logout', 'AuthController@logout');
        Route::get('user', 'AuthController@user');
    });
});

Create Controller

Chúng ta tạo 1 controller là AuthController

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\User;
class AuthController extends Controller
{
    /**
     * Create user
     *
     * @param  [string] name
     * @param  [string] email
     * @param  [string] password
     * @param  [string] password_confirmation
     * @return [string] message
     */
    public function signup(Request $request)
    {
        $request->validate([
            'name' => 'required|string',
            'email' => 'required|string|email|unique:users',
            'password' => 'required|string|confirmed'
        ]);
        $user = new User([
            'name' => $request->name,
            'email' => $request->email,
            'password' => bcrypt($request->password)
        ]);
        $user->save();
        return response()->json([
            'message' => 'Successfully created user!'
        ], 201);
    }
  
    /**
     * Login user and create token
     *
     * @param  [string] email
     * @param  [string] password
     * @param  [boolean] remember_me
     * @return [string] access_token
     * @return [string] token_type
     * @return [string] expires_at
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string',
            'remember_me' => 'boolean'
        ]);
        $credentials = request(['email', 'password']);
        if(!Auth::attempt($credentials))
            return response()->json([
                'message' => 'Unauthorized'
            ], 401);
        $user = $request->user();
        $tokenResult = $user->createToken('Personal Access Token');
        $token = $tokenResult->token;
        if ($request->remember_me)
            $token->expires_at = Carbon::now()->addWeeks(1);
        $token->save();
        return response()->json([
            'access_token' => $tokenResult->accessToken,
            'token_type' => 'Bearer',
            'expires_at' => Carbon::parse(
                $tokenResult->token->expires_at
            )->toDateTimeString()
        ]);
    }
  
    /**
     * Logout user (Revoke the token)
     *
     * @return [string] message
     */
    public function logout(Request $request)
    {
        $request->user()->token()->revoke();
        return response()->json([
            'message' => 'Successfully logged out'
        ]);
    }
  
    /**
     * Get the authenticated User
     *
     * @return [json] user object
     */
    public function user(Request $request)
    {
        return response()->json($request->user());
    }
}

Testing

Chúng ta sử dụng Postman để test api nhé, trong phần Header mn nhớ có key này:

 Content-Type: application/json
X-Requested-With: XMLHttpRequest
Test register
Test login and get access token
Test get info user
Test logout

 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ị trong Laravel , 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. Như vậy mình đã nói về hai cách xác thực cho api. Laravel Passport cung cấp triển khai máy chủ OAuth2 đầy đủ cho ứng dụng Laravel. Sanctum  lại là một package đơn giản để triển khai token API cho người dùng của bạn mà không phức tạp như OAuth. Sanctum sử dụng các dịch vụ xác thực phiên dựa trên cookie được tích hợp sẵn của Laravel. Nếu chưa biết về Laravel Santum, bạn có thể tham khảo bài viết này https://tech.miichisoft.net/laravel-sanctum/.

Tài liệu tham khảo:

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.