API Authentication (Passport)

Giới thiệu

Laravel đã sẵn có việc thực hiện authentication qua login form, nhưng về APIs thì sao? APIs thường sử dụng tokens để authenticate người dùng và không duy trì session giữa các lần request. Laravel sử dụng Laravel Passport để làm API authentication, nó cung cấp đầy đủ OAuth2 server để thực hiện ứng dụng của bạn trong khoảng 1 phút. Passport dược xây dựng trên League OAuth2 server của tác giả Alex Bilbie.

Tài liệu này giả sử bạn đã biết về OAuth2. Nếu bạn không biết gì về OAuth2, thì hãy tìm hiểu các tính năng của nó OAuth2 trước khi bắt đầu.

Cài đặt

Để bắt đầu, cài Passport qua Composer package manager:

composer require laravel/passport

Tiếp theo, đăng ký Passport service provider trong mảng providers của file cấu hình config/app.php:

Laravel\Passport\PassportServiceProvider::class,

The Passport service provider đăng ký nó với database migration của framework, vì vậy bạn nên chạy migrate cơ sở dữ liệu của bạn sau khi đăng ký với provider. The Passport migrations sẽ tạo ra những bảng cần thiết để lưu token của clients và access tokens:

php artisan migrate

Tiếp theo, bạn chạy lệnh passport:install. Lệnh này sẽ tạo ra môt key mã hóa cần thiết để sinh ra access tokens. Ngoài ra, lệnh này nó sẽ tạo "personal access" và "password grant" clients nó sử dụng để sinh ra access tokens:

php artisan passport:install

Say khi chạy lệnh trên, thêm trait Laravel\Passport\HasApiTokens vào trong model App\User. Trait sẽ cung cấp một số hàm hữu ích cho model của bạn để nó cho phép bạn kiểm tra xác thực token và scope của người dùng:

<?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 theo, bạn gọi phương thức Passport::routes trong phương thức boot của AuthServiceProvider. Phương thức này sẽ đăng ký những route cần thiết để xử lý access tokens và revoke access tokens, clients, và 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, trong file cấu hình config/auth.php, bạn cần phải đặt driver của api authentication guard thành passport. Nó sẽ bảo ứng dụng của bạn sử dụng TokenGuard của Passport khi xác thực những request API đến:

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

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

Frontend Quickstart

Để sử dụng Passport Vue components, bạn cần phải sử dụng JavaScript framework Vue. Những components còn sử dụng Bootstrap CSS framework. Tuy nhiên, nếu bạn không sử dụng những tool đó, components serve như là một tài liệu để bạn thực hiện phía frontend.

Passport mang theo một JSON API mà bạn có thể sử dụng để cho phép người dùng tạo clients và personal access tokens. Tuy nhiên, nó có thể tốn nhiều thời gian để code một frontend tương tác với APIs. Vì vậy, Passport ngoài ra còn cung cấp Vue components mà bạn có thể sử dụng như một ví dụ để thực hiện hoặc là điểm bắt đầu để thực hiện ứng dụng.

Để publish Passport Vue components, sử dụng lệnh vendor:publish Artisan:

php artisan vendor:publish --tag=passport-components

Những components được published sẽ được lưu tại thư mục resources/assets/js/components. Khi components đã được published, bạn cần phải đăng ký chúng trong file resources/assets/js/app.js:

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')
);

Khi components đã được đăng ký, bạn có thể ném chúng trong file templates của ứng dụng để bắt đầu tạo clients và personal access tokens:

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

Cấu hình

Thời gian sống của token

Mặc định, Passport để thời gian sống của access tokens là bất tử. Nếu bạn muốn thời gian sống của token ngắn hơn, bạn có thể sử dụng phương thức tokensExpireInrefreshTokensExpireIn. Những phương thức đó được gọi 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));
}

Pruning Revoked Tokens

Mặc định, Passport không xóa các revoked access tokens trong cơ sở dữ liệu. Vì thế, một lương lớn các tokens có thể làm tăng kích thước cơ sử dữ liệu. Nếu bạn muốn Passport tự động xóa revoked tokens, bạn có thể gọi phương thức pruneRevokedTokenstrong phương thức boot của AuthServiceProvider:

use Laravel\Passport\Passport;

Passport::pruneRevokedTokens();

Phương thức này sẽ không tự động xóa ngay revoked tokens. Mà, revoked tokens sẽ được xóa khi người dùng gửi một access token mới hoặc refreshes một token đã tồn tại.

Issuing Access Tokens

Sử dụng OAuth2 với authorization là quen thuộc với các lập trinh viên dối với OAuth2. Khi sử dụng authorization codes, một ứng dụng client sẽ redirect một người dùng đến serverh, nơi mà họ sẽ chấp nhận hoặc từ chối request xử lý một access token đến client.

Quản lý Clients

Đầu tiên, các lập trình viên xây dựng ứng dụng cần tương tác với API của ứng dụng sẽ cần phải đăng ký với ứng dụng với bạn để tạo ra một "client". Thông thường, nó bao gồm tên của ứng dụng và một URL ứng dụng có có thể redirect sau khi chấp nhận request cho authorization.

Lệnh passport:client

Cách đơn giản nhất để tạo một client là sử dụng lệnh passport:client Artisan. Lệnh này có thể được sử dụng để tạo những client của chính bạn để testing OAuth2. Khi bạn chạy lệnh client, Passport sẽ báo cho bạn một số thông tin về client và cung cấp cho bạn một client ID và một secret:

php artisan passport:client

JSON API

Vì người dùng của bạn không thể sử dụng lệnh client, vì vậy Passport cung cấp một JSON API mà bạn có thể sử dụng để tại clients. Điều này giúp bạn tiết kiệm được thời giản vào những việc code controllers những chức năng tạo mới, cập nhật, và xóa clients.

Tuy nhiên, bạn cần phải ghép JSON API của Passport với frontend của bạn để cung cấp một bảng điều khiển cho người dùng để quản lý client của họ. Bên dưới, chúng ta sẽ xem lại tất cả những đầu ra của API của quản lý clients. Để thuận tiện, chúng ta sử dụng Vue để chứng minh tạo HTTP requests đến đầu ra.

Nếu bạn không muốn thực hiện toàn bộ quản lý client frontend, bạn có thể sử dụng Bắt đầu nhanh với frontend để có đầy đủ các chức năng của frontend trong khoảng một phút.

GET /oauth/clients

Route này sẽ trả về tất cả clients để authenticated người dùng. Điểm chính của đầu ra tất cả các client của người dùng là những thằng mà họ có thể sửa hoặc xóa chúng:

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

POST /oauth/clients

Route sử dụng để tạo mới clients. Nó yêu cầu hai miếng dữ liệu: name của client và một URL redirect URL. The redirect nơi mà người dùng sẽ được chuyển đến sau khi phê duyệt hoặc từ chối một request cho authorization.

Khi một client được tạo, nó sẽ được cấp một client ID và một client secret. Giá trị này sẽ được sử dụng khi request access tokens từ ứng dụng. Client tạo route sẽ trả về một thể hiên client mới:

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

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

PUT /oauth/clients/{client-id}

Route được sử dụng để cập nhật clients. Nó yêu cầu 2 mẩu dữ liệu: name của client và URL redirect . URL redirect nơi mà người dùng sẽ bị redirect sau khi phê duyệt hoặc từ chối một request cho authorization. Route sẽ trả về một thể hiện client được cập nhật:

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

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

DELETE /oauth/clients/{client-id}

Route được sử dụng khi xóa clients:

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

Requesting Tokens

Redirecting For Authorization

Khi một client đã được tạo, các lập trình viên có thể sử dụng client ID và secret của nó để gửi request mã authorization và access token từ ứng dụng. Đầu tiên, ứng dụng cần được tạo một redirect request đến /oauth/authorize route của ứng dụng như sau:

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

Nhớ rằng, route /oauth/authorize đã được định nghĩa trong phương thức Passport::routes. Bạn không cần phải định nghĩa nó trong route.

Phê duyệt request

Khi nhận được authorization requests, Passport sẽ tự động hiện thị một template cho người dùng cho phép chúng xác minh hoặc từ chối authorization request. Nếu request dược chấp nhận, chúng sẽ được chuyển trang quay lại redirect_uri được quy định bởi ứng dụng. Tham số redirect_uri phải khớp với URL redirect đã được chỉ định khi client được tạo.

Nếu bạn muốn tùy biến authorization approval screen, bạn phải tạo view Passport bằng lệnh vendor:publish Artisan. View tạo ra sẽ nằm tại thư mục resources/views/vendor/passport:

php artisan vendor:publish --tag=passport-views

Chuyển mã Authorization thành Access Tokens

Nếu người dùng xác minh authorization request, họ sẽ được chuyên trang quay lại trang consumer của ứng dụng. Consumer nên tạo một request POST vào ứng dụng thành một request access token. Request nên gồm mã authorization được tạo khi người dùng xác minh authorization request. Trong ví dụ này, chúng ta sẽ sử dụng thư viện Guzzle HTTP để tạo POST request:

Route::get('/callback', function (Request $request) {
    $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,
        ],
    ]);

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

Route /oauth/token sẽ trả về một JSON response chứa access_token, refresh_token, và thuộc tính expires_in. Thuộc tính expires_in chứa số giây mà access token sẽ hết hạn.

Giống như route /oauth/authorize, route /oauth/token được định nghĩa cho bạn bởi phương thức Passport::routes. Nó không cần được định nghĩa trong route nữa.

Refreshing Tokens

Nếu ứng dụng của bạn có short-lived access tokens, người dùng cần phải refresh access tokens của họ qua refresh token nó được cung cấp cho chúng khi access token hết hạn. Trong ví dụ này, chúng ta sẽ sử dụng thư viện Guzzle HTTP để refresh token:

$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);

Route /oauth/token sẽ trả về một JSON response chứa access_token, refresh_token, và thuộc tính expires_in. Thuộc tính expires_inchứa số giây để access token hết hạn.

Password Grant Tokens

OAuth2 password grant allows your other first-party clients, such as a mobile application, to obtain an access token using an e-mail address / username and password. This allows you to issue access tokens securely to your first-party clients without requiring your users to go through the entire OAuth2 authorization code redirect flow.

Creating A Password Grant Client

Before your application can issue tokens via the password grant, you will need to create a password grant client. You may do this using the passport:client command with the --password option. If you have already run the passport:install command, you do not need to run this command:

php artisan passport:client --password

Requesting Tokens

Once you have created a password grant client, you may request an access token by issuing a POST request to the /oauth/token route with the user's email address and password. Remember, this route is already registered by the Passport::routes method so there is no need to define it manually. If the request is successful, you will receive an access_token and refresh_token in the JSON response from the server:

$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);

Remember, access tokens are long-lived by default. However, you are free to configure your maximum access token lifetime if needed.

Requesting All Scopes

When using the password grant, you may wish to authorize the token for all of the scopes supported by your application. You can do this by requesting the * scope. If you request the * scope, the can method on the token instance will always return true. This scope may only be assigned to a token that is issued using the password grant:

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'username' => 'taylor@laravel.com',
        'password' => 'my-password',
        'scope' => '*',
    ],
]);

Personal Access Tokens

Sometimes, your users may want to issue access tokens to themselves without going through the typical authorization code redirect flow. Allowing users to issue tokens to themselves via your application's UI can be useful for allowing users to experiment with your API or may serve as a simpler approach to issuing access tokens in general.

Personal access tokens are always long-lived. Their lifetime is not modified when using the tokensExpireIn or refreshTokensExpireIn methods.

Creating A Personal Access Client

Before your application can issue personal access tokens, you will need to create a personal access client. You may do this using the passport:client command with the --personal option. If you have already run the passport:install command, you do not need to run this command:

php artisan passport:client --personal

Managing Personal Access Tokens

Once you have created a personal access client, you may issue tokens for a given user using the createToken method on the User model instance. The createToken method accepts the name of the token as its first argument and an optional array of scopes as its second argument:

$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;

JSON API

Passport also includes a JSON API for managing personal access tokens. You may pair this with your own frontend to offer your users a dashboard for managing personal access tokens. Below, we'll review all of the API endpoints for managing personal access tokens. For convenience, we'll use Vue to demonstrate making HTTP requests to the endpoints.

If you don't want to implement the personal access token frontend yourself, you can use the frontend quickstart to have a fully functional frontend in a matter of minutes.

GET /oauth/scopes

This route returns all of the scopes defined for your application. You may use this route to list the scopes a user may assign to a personal access token:

this.$http.get('/oauth/scopes')
    .then(response => {
        console.log(response.data);
    });

GET /oauth/personal-access-tokens

This route returns all of the personal access tokens that the authenticated user has created. This is primarily useful for listing all of the user's token so that they may edit or delete them:

this.$http.get('/oauth/personal-access-tokens')
    .then(response => {
        console.log(response.data);
    });

POST /oauth/personal-access-tokens

This route creates new personal access tokens. It requires two pieces of data: the token's name and the scopes that should be assigned to the token:

const data = {
    name: 'Token Name',
    scopes: []
};

this.$http.post('/oauth/personal-access-tokens', data)
    .then(response => {
        console.log(response.data.accessToken);
    })
    .catch (response => {
        // List errors on response...
    });

DELETE /oauth/personal-access-tokens/{token-id}

This route may be used to delete personal access tokens:

this.$http.delete('/oauth/personal-access-tokens/' + tokenId);

Protecting Routes

Via Middleware

Passport includes an authentication guard that will validate access tokens on incoming requests. Once you have configured the api guard to use the passport driver, you only need to specify the auth:api middleware on any routes that require a valid access token:

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

Passing The Access Token

When calling routes that are protected by Passport, your application's API consumers should specify their access token as a Bearer token in the Authorization header of their request. For example, when using the Guzzle HTTP library:

$response = $client->request('GET', '/api/user', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer '.$accessToken,
    ],
]);

Token Scopes

Defining Scopes

Scopes allow your API clients to request a specific set of permissions when requesting authorization to access an account. For example, if you are building an e-commerce application, not all API consumers will need the ability to place orders. Instead, you may allow the consumers to only request authorization to access order shipment statuses. In other words, scopes allow your application's users to limit the actions a third-party application can perform on their behalf.

You may define your API's scopes using the Passport::tokensCan method in the boot method of your AuthServiceProvider. The tokensCan method accepts an array of scope names and scope descriptions. The scope description may be anything you wish and will be displayed to users on the authorization approval screen:

use Laravel\Passport\Passport;

Passport::tokensCan([
    'place-orders' => 'Place orders',
    'check-status' => 'Check order status',
]);

Assigning Scopes To Tokens

When Requesting Authorization Codes

When requesting an access token using the authorization code grant, consumers should specify their desired scopes as the scope query string parameter. The scope parameter should be a space-delimited list of scopes:

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

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

When Issuing Personal Access Tokens

If you are issuing personal access tokens using the User model's createToken method, you may pass the array of desired scopes as the second argument to the method:

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

Checking Scopes

Passport includes two middleware that may be used to verify that an incoming request is authenticated with a token that has been granted a given scope. To get started, add the following middleware to the $routeMiddleware property of your app/Http/Kernel.php file:

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,

Check For All Scopes

The scopes middleware may be assigned to a route to verify that the incoming request's access token has all of the listed scopes:

Route::get('/orders', function () {
    // Access token has both "check-status" and "place-orders" scopes...
})->middleware('scopes:check-status,place-orders');

Check For Any Scopes

The scope middleware may be assigned to a route to verify that the incoming request's access token has at least one of the listed scopes:

Route::get('/orders', function () {
    // Access token has either "check-status" or "place-orders" scope...
})->middleware('scope:check-status,place-orders');

Checking Scopes On A Token Instance

Once an access token authenticated request has entered your application, you may still check if the token has a given scope using the tokenCan method on the authenticated User instance:

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('place-orders')) {
        //
    }
});

Consuming Your API With JavaScript

When building an API, it can be extremely useful to be able to consume your own API from your JavaScript application. This approach to API development allows your own application to consume the same API that you are sharing with the world. The same API may be consumed by your web application, mobile applications, third-party applications, and any SDKs that you may publish on various package managers.

Typically, if you want to consume your API from your JavaScript application, you would need to manually send an access token to the application and pass it with each request to your application. However, Passport includes a middleware that can handle this for you. All you need to do is add the CreateFreshApiToken middleware to your web middleware group:

'web' => [
    // Other middleware...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

This Passport middleware will attach a laravel_token cookie to your outgoing responses. This cookie contains an encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. Now, you may make requests to your application's API without explicitly passing an access token:

this.$http.get('/user')
    .then(response => {
        console.log(response.data);
    });

When using this method of authentication, you will need to send the CSRF token with every request via the X-CSRF-TOKEN header. Laravel will automatically send this header if you are using the default Vue configuration that is included with the framework:

Vue.http.interceptors.push((request, next) => {
    request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);

    next();
});

If you are using a different JavaScript framework, you should make sure it is configured to send this header with every outgoing request.

Nguồn: https://laravel.com/docs/5.3/passport