Xác thực API bằng OAuth 2 với Laravel Passport

Trong các ứng dụng phần mềm hiện đại, các web API là không thể thiếu, có rất nhiều các mô hình ứng dụng sử dụng web API như mô hình server-to-server, hay mô hình SPA (Single Page Application). 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 khỏi những con mắt tò mò, điều này với các hệ thống truyền thống rất đơn giản còn với API thì sao? Laravel tạo ra một gói thư viện Passport giúp thực hiện xác thực trong API đơn giản đơ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.

Gợi ý:

Cơ bản về OAuth 2, bạn nên đọc trước khi bắt đầu với Laravel Passport.

Phương châm bài viết này cũng như các bài viết khác, chúng ta sẽ vừa học lý thuyết và thực hành cùng các ví dụ luôn, như vậy sẽ Hiểu sâu và nhớ lâu, vần vãi :)).

1. Cài đặt Laravel Passport

Chúng ta bắt đầu với việc cài đặt Laravel Passport thông qua Composer do gói thư viện này không được tích hợp sẵn cùng với framework Laravel.

c:\my-lapt>composer require laravel/passport
Using version ^2.0 for laravel/passport
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 12 installs, 0 updates, 0 removals
  - Installing phpseclib/phpseclib (2.0.5): Loading from cache
  - Installing psr/http-message (1.0.1): Loading from cache
...

Tiếp theo là những công việc chúng ta thường làm khi cài đặt một gói thư viện mới: Đăng ký provider trong config/app.php

'providers' => [
    ...
    Laravel\Passport\PassportServiceProvider::class,
    ...
],

Laravel Passport được thiết kế với một số các bảng trong cơ sở dữ liệu, bạn chỉ cần thực hiện lệnh artisan migrate. Passport đã đăng ký sẵn các bảng này trong framework, do đó bạn sẽ không tìm thấy các file tạo bảng trong thư mục database/migrates đâu.

c:\my-lapt>php artisan migrate
Migrating: 2016_06_01_000001_create_oauth_auth_codes_table
Migrated:  2016_06_01_000001_create_oauth_auth_codes_table
Migrating: 2016_06_01_000002_create_oauth_access_tokens_table
Migrated:  2016_06_01_000002_create_oauth_access_tokens_table
Migrating: 2016_06_01_000003_create_oauth_refresh_tokens_table
Migrated:  2016_06_01_000003_create_oauth_refresh_tokens_table
Migrating: 2016_06_01_000004_create_oauth_clients_table
Migrated:  2016_06_01_000004_create_oauth_clients_table
Migrating: 2016_06_01_000005_create_oauth_personal_access_clients_table
Migrated:  2016_06_01_000005_create_oauth_personal_access_clients_table

Ok, bước tiếp theo với câu lệnh artisan passport:install, sẽ tạo ra các khóa mã hóa dùng trong việc sinh ra các thẻ truy nhập (access token). Nó tạo ra hai khóa mã hóa là personal access và password grant.

c:\my-lapt>php artisan passport:install
Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client Secret: erCzNy1k4G9rgHIc71xY9oyDfAoiEFX06w29audt
Password grant client created successfully.
Client ID: 2
Client Secret: xYedgLyFXXDuPXiZneBRqXW07nbHnLAGuvyoftfi

Các thông tin này bạn có thể lưu để dùng lại hoặc có thể lấy lại trong bảng oauth_clients trong database. Sau khi thực hiện câu lệnh này cần thêm trait Laravel\Passport\HasApiTokens và model App\User.php

<?php

namespace App;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

Tiếp đó gọi phương thức routes() trong phương thức boot() của app\Providers\AuthServiceProvider.php.

<?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à driver cho api trong config/auth.php

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

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

2. Frontend được xây dựng sẵn trong Laravel Passport

Passport có sẵn các JSON API cho việc tạo các client (client id và client secret) và access token, tuy nhiên nó cũng hỗ trợ các giao diện fontend để làm việc này. Laravel Passport đã xây dựng sẵn một số Vue component sử dụng cho các công việc trong OAuth 2. Thưc hiện publish các component này bằng câu lệnh vendor:publish như sau:

c:\my-lapt>php artisan vendor:publish --tag=passport-components
Copied Directory [\vendor\laravel\passport\resources\assets\js\components] To [\
resources\assets\js\components\passport]
Publishing complete.

Các Vue component được publish sẽ nằm trong thư mục resources\assets\js\components. Muốn sử dụng chúng cần khai báo trong resources/assets/js/app.js là điểm bắt đầu của ứng dụng Vue.js như sau:

Vue.component(
    'passport-clients',
    require('./components/passport/Clients.vue')
);

Vue.component(
    'passport-authorized-clients',
    require('./components/passport/AuthorizedClients.vue')
);

Vue.component(
    'passport-personal-access-tokens',
    require('./components/passport/PersonalAccessTokens.vue')
);

Sau khi đăng ký, thực hiện biên dịch lại bằng Laravel Mix với câu lệnh npm run dev.

npm run dev

Tiếp đó, đưa các template vào bất kỳ đâu bạn muốn hiển thị các chức năng này.

<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>

3. Cài đặt môi trường thực hành

Tạm dừng lại lý thuyết, chúng ta sẽ cài đặt môi trường thực hành để thực hành với các dạng ủy quyền trong phần kế tiếp. Mô hình môi trường thực hành như sau:

Mô hình thực hành với OAuth 2 và Laravel Passport

Như trong mô hình OAuth 2, chúng ta thấy có 4 đối tượng, ở đây chúng ta sẽ xây dựng 2 hệ thống là Consumer.dev chính là Ứng dụng khách và Passport.dev là nơi cung cấp các dịch vụ API (bao gồm cả hai đối tượng: máy chủ ủy quyền và máy chủ tài nguyên). Ok, chúng ta sẽ tạo ra hai project Laravel có tên là consumer và passport, sau đó bạn có thể tạo các tên miền ảo cục bộ cho hai project này (Xem hướng dẫn tạo tên miền ảo cục bộ).

3.1 Tạo project passport.dev

  1. Tạo project sử dụng Laravel, tham khảo Cài đặt nhanh Laravel cho Windows -> composer new-project laravel/laravel my-lapt hoặc laravel new my-lapt
  2. Cài đặt gói laravel/passport -> composer require laravel/passport
  3. Đăng ký provider trong config/app.php.
  4. Migrate tạo bảng trong CSDL -> php artisan migrate, trước đó nhớ cấu hình kết nối database.
  5. Tạo client (client id và client secret) -> php artisan passport:install
  6. Thêm trait Laravel\Passport\HasApiTokens vào app\User.php
  7. Đăng ký route mặc định cho Laravel Passport trong AuthServiceProvider.php -> Passport::routes().
  8. Thiết lập driver cho api trong config/auth.php.
  9. Publish Vue component xây dựng sẵn -> php artisan vendor:publish –tag=passport-components.
  10. Khai báo các component vào app.js
  11. Biên dịch tài nguyên bằng Laravel Mix -> npm run dev (nếu tạo project mới chưa chạy npm install lần nào thì cần chạy lệnh npm install trước).
  12. Chạy Laravel Authentication -> php artisan make:auth, tạo user bằng Laravel Seeding -> php artisan make:seeder UsersTableSeeder, thêm code tạo user và chạy php artisan db:seed để đưa dữ liệu vào.
  13. Thêm các template vào view resources/views/home.blade.php để khi đăng nhập thấy các giao diện được cài đặt sẵn trong Laravel.

3.2 Tạo project consumer.dev

Tạo một project Laravel bình thường và cài đặt gói Guzzle HTTP bằng câu lệnh

composer require guzzlehttp/guzzle

Chú ý: Do quá trình tạo hai project này khá nhiều bước, bạn nào gặp vấn đề hãy comment ở dưới mình sẽ trả lời ngay khi có thể. Trong thời gian tới, mình sẽ quay lại màn hình phần này để upload lên Youtube giúp bạn thực hiện trực quan hơn.

4. Các dạng ủy quyền trong Laravel Passport

Chú ý:

Bạn nên đọc Cơ bản về OAuth 2 để nắm được các thuật ngữ, khái niệm sử dụng trong bài.

4.1 Cấp ủy quyền bằng mã ủy quyền

Chúng ta cùng thực hiện các bước sử dụng trong cấp quyền bằng mã ủy quyền. Ví dụ như sau:

Thực hiện một ví dụng giống như khi Tích hợp đăng nhập vào Facebook, hay Google, Twiter…, ở project consumer sẽ thực hiện tích hợp đăng nhập cấp bởi project passport.

Bước 1: Vào http://passport.dev đăng ký ứng dụng (client bao gồm client_id và client_secret) giống như vào trang dành cho nhà phát triển của Facebook.

Giao diện trang passport có nút tạo mới client

Nhập thông tin đăng ký bao gồm: application name, application website, redirect uri (Giao diện xây dựng sẵn của Laravel bỏ qua application website, bạn có thể tự thêm vào).

Nhập thông tin đăng ký tạo mới client trong passport

Click vào Create, passport.dev sẽ tạo ra Client Id và Client Secret cho ứng dụng consumer.

Passport tạo Client Id và Client Secret cho ứng dụng của bạn

Bước 2: Xây dựng callback cho consumer.

Trong project Consumer, chúng ta tạo ra đường dẫn đăng nhập thông qua Passport là http://consumer.dev/auth/passport.

Thêm route vào routes/web.php

use Illuminate\Http\Request;

Route::get('/auth/passport', function () {
     $query = http_build_query([
        'client_id' => '4',
        'redirect_uri' => 'http://consumer.dev/callback',
        'response_type' => 'code',
        'scope' => ''
      ]);

     return redirect('http://passport.dev/oauth/authorize?'.$query);
});

Route::get('/callback', function (Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://passport.dev/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => '4',
            'client_secret' => 'fuWkWtgITxQd0LqkrTFbZxkeCrtPoGnMndnp3kzJ',
            'redirect_uri' => 'http://consumer.dev/callback',
            'code' => $request->code
        ],
    ]);

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

Bước 3: Test

Bạn xem luồng thực hiện ủy quyền thông qua mã ủy quyền trong OAuth 2, ở đây chúng ta sẽ kiểm tra theo đúng các bước được đưa ra (13 bước).

1) Người dùng truy nhập ứng dụng consumer.dev

2) Consumer.dev hiển thị link “Đăng nhập bằng Passport” với đường dẫn là http://consumer.dev/auth/passport.

3) Người dùng nhấn vào đây, nó sẽ chuyển hướng đến request

https://passport.dev/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=

nhưng do yêu cầu này đòi hỏi phải xác thực nên nó chuyển hướng tiếp đến trang đăng nhập của Passport (chú ý, đây là đăng nhập của Passport.dev không phải của Consumer.dev do đó Consumer.dev không có thông tin về tài khoản người dùng). Giả sử chúng ta đã tạo tài khoản cho người dùng này là user1@passport.com/123456

4) Sau khi đăng nhập Passport.dev sẽ chuyển hướng về request ở trên và hiện ra thông báo người dùng cần ủy quyền cho ứng dụng Consumer.dev (Giống như khi thực hiện với Facebook, Google…)

Thông báo người dùng cần ủy quyền cho ứng dụng consumer

5) Người dùng ủy quyền cho ứng dụng khi bấm vào Authorize và Passport.dev chuyển hướng tiếp đến đường dẫn

http://consumer.dev/callback?code=AUTHORIZATION_CODE

6) Như vậy ứng dụng Consumer.dev đã nhập được mã ủy quyền và thực hiện POST đến đường dẫn

http://passport.dev/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL

Chính là đoạn code này:

    $response = $http->post('http://passport.dev/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => '4',
            'client_secret' => 'fuWkWtgITxQd0LqkrTFbZxkeCrtPoGnMndnp3kzJ',
            'redirect_uri' => 'http://consumer.dev/callback',
            'code' => $request->code
        ],
    ]);

7) Passport.dev kiểm tra thông tin Client Id, Client Secret kèm theo mã ủy quyền, nếu ok nó trả về access token và refresh token.

Passport trả về access token và refresh token cho consumer

8) Ở đây, consumer đã có được access token và refresh token do đó nó biết được người dùng đã xác thực.

9 – 13) Consumer muốn lấy thông tin về người dùng có thể thực hiện gửi GET đến http://passport.dev/api/user với header có dạng Authorization: Bearer AUTHORIZATION_CODE.

Sửa lại routes/web.php trên Consumer như sau:

<?php

use Illuminate\Http\Request;

Route::get('/', function () {
     $query = http_build_query([
        'client_id' => '4',
        'redirect_uri' => 'http://consumer.dev/callback',
        'response_type' => 'code',
        'scope' => ''
      ]);

     return redirect('http://passport.dev/oauth/authorize?'.$query);
});

Route::get('/callback', function (Request $request) {
    $http = new GuzzleHttp\Client;

    $response = $http->post('http://passport.dev/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => '4',
            'client_secret' => 'fuWkWtgITxQd0LqkrTFbZxkeCrtPoGnMndnp3kzJ',
            'redirect_uri' => 'http://consumer.dev/callback',
            'code' => $request->code,
        ],
    ]);

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

    $response = $http->get('http://passport.dev/api/user', [
    	'headers' => [
            'Authorization' => 'Bearer ' . $body['access_token'],
        ],
    ]);

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

Chú ý, ở đây máy chủ ủy quyền và máy chủ tài nguyên cùng là một và chính là Passport.dev. Khi thực hiện lại bạn sẽ thấy Consumer.dev đã lấy được thông tin người dùng khi gửi access token đến máy chủ tài nguyên.

Lấy dữ liệu từ máy chủ tài nguyên bằng việc gửi access token

4.2 Ủy quyền ngầm định (Implicit Grant Token)

Ủy quyền ngầm định tương tự như ủy quyền thông qua mã ủy quyền, tuy nhiên access token được gửi trực tiếp về client mà không thông qua mã ủy quyền. Loại ủy quyền này thường được sử dụng trong Javascript và các ứng dụng mobile, nơi mà thông tin bí mật của client không thể lưu trữ bảo mật. Để sử dụng loại ủy quyền này, cần gọi đến phương thức enableImplicitGrant trong AuthServiceProvider:

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

    Passport::routes();

    Passport::enableImplicitGrant();
}

Khi loại ủy quyền này được kích hoạt, chúng ta có thể sử dụng client ID để yêu cầu access token bằng cách gửi request đến /oauth/authorize như sau:

Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'token',
        'scope' => '',
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

4.3 Ủy quyền theo thông tin người dùng (Resource Owner Password Credentials hay Password Grant)

OAuth 2 Resource Owner Password Credential (ủy quyền theo thông tin người dùng) cho phép các ứng dụng bên thứ nhất như các ứng dụng điện thoại có thể lấy access token sử dụng username/password. Nó tạo ra trực tiếp access token cho ứng dụng mà không cần sử dụng authorization code (mã ủy quyền).

 

Trước khi ứng dụng có thể sinh ra access token thông qua ủy quyền theo thông tin người dùng, bạn cần tạo ra một client (client id và client secret). Sử dụng câu lệnh passport:client với tùy chọn –password. Nếu đã thực hiện câu lệnh passport:install ở bước cài đặt, bạn không cần chạy thêm lệnh trên.

php artisan passport:client --password

Khi đã tạo ra client, chúng ta có thể yêu cầu một access token thông qua một request POST đến đường dẫn /oauth/token cùng với username và password. Chú ý, route này đã được đăng ký bằng Passport::routes() trong AuthServiceProvider.php trong bước cài đặt, do đó không cần thêm route vào routes\api.php. Nếu request thành công, sẽ nhận được kết quả dạng JSON có chứa access token và refresh token.

$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' => 'taylor@laravel.com',
        'password' => 'my-password',
        'scope' => '',
    ],
]);

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

Loại ủy quyền này thường sử dụng khi Consumer.dev là một thành viên trong hệ thống trong đó Passport.dev cũng là thành viên, tức là Consumer.dev đã là người trong nhà cùng với Passport.dev, do đó người dùng có thể gửi username/password đến cho Consumer.dev mà không ngần ngại. Thực tế, ví dụ bạn đã có một website có CSDL khách hàng, khi bạn tạo một website khác mà muốn sử dụng lại CSDL khách hàng này để đăng nhập bạn có thể sử dụng loại ủy quyền này.

Bước 1: Để thực hành loại ủy quyền này, chúng ta tạo ra một form đăng nhập trên Consumer.dev.

Chú ý:

  1. Trên Passport cũng đã có form đăng nhập nhưng chúng ta muốn đăng nhập từ Consumer)
  2. Form đăng nhập trên Consumer không sử dụng Laravel Authentication là các tính năng xây dựng sẵn cho xác thực trong Laravel.

Tạo view resources/view/normal-login.blade.php với nội dung.

<!DOCTYPE html>
<html>
<head>
  <title>Allaravel Test</title>
  <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
  <div class="wrapper">
    <div class="row">
      <div class="col-md-6 col-md-push-3">
        <div class="panel panel-default">
          <div class="panel-heading">
            <strong>Login</strong>
          </div>
          <div class="panel-body">
            <form action="http://consumer.dev/auth/normal" method="GET">
              <div class="form-group">
                <label>Email Address</label>
                <input class="form-control" placeholder="Enter your email address" type="text" v-model="login.email">
              </div>
              <div class="form-group">
                <label>Password</label>
                <input class="form-control" placeholder="Enter your email address" type="password" v-model="login.password">
              </div>
              <button class="btn btn-primary" type="submit">Login</button>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>

Bước 2: Thêm route vào routes/web.php

Route::get('/login', function () {
	return view('normal-login');
});

Route::get('/auth/normal', function (Request $request) {
	$http = new GuzzleHttp\Client;

	$response = $http->post('http://passport.dev/oauth/token', [
	    'form_params' => [
	        'grant_type' => 'password',
	        'client_id' => '2',
	        'client_secret' => 'dWMEqIfKYZJHop71TxrnNs4EM1FOU3MSRUxNndPB',
	        'username' => 'user1@gmail.com',
	        'password' => '123456',
	        'scope' => '',
	    ],
	]);

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

Chú ý, form đăng nhập này sẽ GET đến http://consumer.dev/auth/normal và xử lý tiếp bằng cách gửi POST đến Passport.dev.

Bước 3: Test

Khi vào http://consumer.dev/login, chúng ta có trang login, cần nhắc lại các chú ý để các bạn khỏi nhầm lẫn:

  1. Trên Passport cũng đã có form đăng nhập nhưng chúng ta muốn đăng nhập từ Consumer)
  2. Form đăng nhập trên Consumer không sử dụng Laravel Authentication là các tính năng xây dựng sẵn cho xác thực trong Laravel.

Trên consumer.dev có thể có database lưu các thông tin người dùng đăng nhập, tùy thuộc vào thiết kế ứng dụng của bạn. Thực hiện đăng nhập bằng tài khoản ở trên user1@passport.com/123456. Kết quả chúng ta sẽ thấy

Kết quả ủy quyền theo thông tin người dùng

Như vậy, đã bỏ qua bước gửi mã ủy quyền đúng như luồng thực hiện trong OAuth 2.

4.4 Ủy quyền theo thông tin ứng dụng (Client Credentials Grant Tokens)

Loại ủy quyền này phù hợp với các xác thực từ máy chủ đến máy chủ, ví dụ bạn sử dụng ủy quyền này trong các job được lập lịch, thực hiện các tác vụ bảo trì thông qua API. Để lấy được access token, chỉ cần gửi một request đến oauth/token:

$guzzle = new GuzzleHttp\Client;

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

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

5. Một số vấn đề khác trong Laravel Passport

Như vậy, chúng ta đã thực hành với một số các loại ủy quyền trong Laravel Passport. Trong quá trình thực hiện, chúng thấy còn một số công việc khác chưa đi vào chi tiết, chúng ta sẽ cùng xem xét ở đây:

5.1 Thời gian hiệu lực của access token

Mặc định, Laravel Passport tạo ra các access token có thời gian hiệu lực dài, chúng ta không bao giờ yêu cầu tạo lại access token bằng refresh token, tuy nhiên trong thiết kế ứng dụng của bạn có thể cần thời gian hiêu lực ngắn, việc này hoàn toàn có thể thực hiện được khi bạn sử dụng các phương thức tokensExpireIn() và refreshTokensExpireIn() trong phương thức boot() của AuthServiceProvider:

use Carbon\Carbon;

/**
 * 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));
}

5.2 Thêm, sửa, xóa Client

Laravel Passport cho phép tạo các Client (Client ID và Client Secret) thông qua câu lệnh artisan passport:client. Ngoài ra, có thể thực hiện được thông qua các JSON API đã được xây dựng sẵn. Trong các ví dụ dưới đây, sẽ sử dụng Axios là một HTTP client dạng Javascript, axios đã được đưa vào sẵn trong cấu hình Laravel (package.json), bạn có thể cài đặt bằng npm install.

5.2.1 Danh sách client

Gửi request GET đến oauth/clients

axios.get('/oauth/clients')
    .then(response => {
        console.log(response.data);
    });

5.2.2 Tạo mới một client

Gửi request POST đến oauth/clients

const data = {
    name: 'Client Name',
    redirect: 'http://example.com/callback'
};

axios.post('/oauth/clients', data)
    .then(response => {
        console.log(response.data);
    })
    .catch (response => {
        // List errors on response...
    });

Chú ý, name là tên của Client, khi Client được tạo ra nó sẽ sinh Client ID và Client Secret gửi lại trong response.

5.2.3 Cập nhật client

Gửi request PUT đến oauth/clients/{client_id}

const data = {
    name: 'New Client Name',
    redirect: 'http://example.com/callback'
};

axios.put('/oauth/clients/' + clientId, data)
    .then(response => {
        console.log(response.data);
    })
    .catch (response => {
        // List errors on response...
    });

5.2.4 Xóa client

Gửi request DELETE đến oauth/clients/{client_id}

axios.delete('/oauth/clients/' + clientId)
    .then(response => {
        //
    });

5.3 Tạo access token

5.3.1 Tạo Personal Access Client

Trước khi tạo ra access token, bạn cần tạo Personal Access Client là một khóa mật mã được sử dụng trong quá trình tạo các access token. Để tạo ra Personal Access Token bạn có thể sử dụng lệnh

php artisan passport:client --personal

Hoặc nếu đã chạy câu lệnh

php artisan passport:install

trong phần cài đặt rồi thì thôi.

5.3.2 Quản lý Personal Access Token

Sử dụng phương thức createToken() của Model User để tạo ra access token

$user = App\User::find(1);

// Creating a token without scopes...
$token = $user->createToken('Token Name')->accessToken;

// Creating a token with scopes...
$token = $user->createToken('My Token', ['place-orders'])->accessToken;

5.4 Bắt buộc xác thực API

Các route cho API được khai báo trong routes/api.php (từ Laravel 5.3 các route được tách biệt từ app\Http\route.php về các file web.php, api.php trong thư mục routes). Để bắt buộc phải xác thực với các API giúp bảo vệ dữ liệu khỏi con mắt tò mò, chúng ta chỉ cần sử dụng Middleware auth:api cho các route này:

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

6. Lời kết

Cuối cùng thì cũng đã kết thúc, tổng kết lại bạn nắm được hai vấn đề chính là hiểu chi tiết hơn về OAuth 2 sau khi học Lý thuyết OAuth 2 và cách sử dụng Laravel Passport để tạo ra các hệ thống hỗ trợ OAuth 2. Bài viết được tổng hợp từ nhiều nguồn kết hợp với kinh nghiệm thực tế, nhưng không tránh khỏi thiếu sót, bạn hãy cùng thảo luận với chúng tôi ở comment dưới đây nhá.

 

 

18 thoughts on “Xác thực API bằng OAuth 2 với Laravel Passport

  1. Mình đang thực hiện Password Grant bằng laravel passport nhưng mỗi khi request cái gì sau khi đã xác thực vẫn cần gửi đi request với headers có Authorization: Bearer ACCESS_TOKEN. Có cách nào ngầm định access token để không phải thiết lập lại nữa không?

    1. Bạn có thể sử dụng consumer API như tài liệu của Laravel như sau:
      Thêm CreateFreshApiToken vào web middleware group trong Kernel.php. Khi đó, Laravel sẽ đưa thêm laravel_token vào header mỗi khi request và bạn không cần phải làm gì thêm. Cứ thực hiện:
      axios.get(‘/api/user’)
      .then(response => {
      console.log(response.data);
      });
      là chạy luôn.

      1. Đã thực hiện thêm middleware CreateFreshApiToken và kiểm tra request header có laravel_token nhưng khi gửi request lỗi 401 Unauthorized, nếu thêm header Authorization: Bearer ACCESS_TOKEN thì lại chạy bình thường bạn à.

        1. Lỗi này bên trong Core Laravel Passport, bạn sửa lại phương thức validCsrf() trong file \Laravel\Passport\Guards\TokenGuard như sau:
          protected function validCsrf($token, $request)
          {
          $compare_token = (string) $request->header(‘X-CSRF-TOKEN’);
          if (empty($compare_token)) {
          $compare_token = (string) $request->header(‘X-XSRF-TOKEN’);
          }
          return isset($token[‘csrf’]) && hash_equals(
          $token[‘csrf’], $this->encrypter->decrypt($compare_token)
          );
          }

  2. Có bạn nào biết cách đăng ký tài khoản thông qua API không? Mình thấy trong Laravel Passport toàn ủy quyền và xác thực là bước sau của bước đăng ký tài khoản.

    1. Theo như mình hiểu thì Laravel Passport chủ yếu là để xác thực API (Tài liệu API đặt tên mục này là API Authentication), tức là nó không liên quan gì đến việc người dùng như Đăng ký, Đăng nhập… hay tóm lại là không liên quan đến RESTful User API. Nếu bạn cần tạo các RESTful API cho user bạn phải tự code thôi.

  3. trang passport.dev của mình không chạy được, bị báo lỗi và mình vẫn chưa fix được.

    This site on the company, organization or school intranet has the same URL as an external website.

    Try contacting your system administrator.
    ERR_ICANN_NAME_COLLISION

    1. Lỗi trên mình sửa được rồi, nhưng bây giờ mình gặp thêm 1 lỗi nữa.
      $body = json_decode((string) $response->getBody(), true);

      $response = $http->get(‘http://passport.dev/api/user’, [
      ‘headers’ => [
      ‘Authorization’ => ‘Bearer ‘ . $body[‘access_token’],
      ],
      ]);
      Đoạn code này, khi mình đưa access token đúng thì báo lỗi 500, khi mình đưa access token sai thì báo lỗi 401.

  4. Bạn cho mình hỏi. Mình sử dụng Vuejs cho chức năng đăng nhập. Method login() như thế này
    login() {

    var data = {
    client_id: 2,
    client_secret: ‘iiXl0DBIMQvJyNrCZMLSm2426aeyFFhlXk2xkmFb’,
    grant_type: ‘password’,
    username: this.email,
    password: this.password,
    scope: ”
    }
    axios.post(‘/oauth/token’, data).then….
    }
    Như vậy nếu người dùng có ý xấu post với 1 scope khác đúng với 1 scope đã đc define thì sẽ có thêm quyền à bạn. Và làm sao để tránh đc việc này.

  5. Lúc trả về mã ủy quyền cho consumer.dev thì bị như này, ai bị lỗi này bảo mình với.
    Server error: `POST http://blog.dev/oauth/token` resulted in a `500 Internal Server Error` response: SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘consumer.oauth_clients’ doesn’t exist (SQL: select * from `oa (truncated…)

    1. Lỗi khá rõ ràng mà bạn “Base table or view not found”, bảng oauth_clients không tồn tại, chắc do bạn chưa thực hiện lệnh php artisan migrate để tạo bảng cần thiết cho laravel passport.

    2. Sorry Hùng, mình đọc không kỹ, bảng oauth_clients bên passport.dev chắc chắn đã có, về logic không cần phải có bảng oauth_clients bên consumer.dev. Lỗi này liên quan đến file .env, nó sử dụng .env của client chứ không sử dụng của server. Chính vì vậy nếu bạn để consumer.dev và passport.dev trên hai máy khác nhau thì sẽ chạy bình thường. Hoặc có thể sử dụng lệnh php artisan config:cache để refresh cache. Chúc bạn may mắn :).

      1. Bạn cho mình hỏi về vấn đề của mình một chút:
        Mình đang có 1 website chạy bằng larvel 5 có tên miền abc.com chẳng hạn, bây giờ mình đang muốn áp dụng react để call api và show dữ liệu trên site. Sau khi người dùng login mình đang dùng session mặc định của laravel để kiểm tra login. Bây giờ sau khi người dùng login mình muốn show tất cả job người dùng đó tạo. Theo lẽ thông thường api phải dạng (/jobs/userId), nhưng mình không hiểu mình làm sao có thể có userId từ client truyền lên, cái này khi đăng nhập nó nằm trong Auth::user nên mình nghĩ api phải get userId và trả về cho mình phải không bạn, mình chỉ cần truyền /jobs ý.

        Sau khi đọc bài của bạn mình đang hiểu mình phải tạo api login sau đó get được token, sau khi get token thì call api /josbs có kèm theo token để lấy jobs theo user đó. Cả cliennt và server mình đang trên 1 site vậy nên xác thực theo loại login nào bạn. Rất mong sự chia sẻ từ bạn. Cảm ơn bạn nhiều

  6. cảm ơn bạn vì các bài viết rất bổ ích
    nhưng bạn có thể hướng dẫn làm 1 website về laravel hoàn chỉnh dc ko bạn ( chẳng hạn như là web bán hàng đa chức năng nè, hihi)

Add Comment