Заметка: В этом уроке я просто хотел продемонстрировать процесс и основные принципы разработки одностраничных приложений. Код будет на скорую руку, без валидации форм, стилей и т.п. Я подразумеваю, что раз вы собираетесь делать одностраничные приложения, то у вас уже есть знания Laravel и Vue.js и вы сами можете позаботиться обо всех этих моментах в своих реальных проектах.

Мы начнем с разработки API. Очень желательно произвести некоторые махинации над только что созданным проектом, если вы собираетесь делать API на Laravel. Например, нужно переписать обработку исключений, чтобы Laravel возвращал пользователям JSON ответы с ошибками вместо стандартных HTML ответов. Однако, этот урок не об этом.

Laravel Passport

Для начала создайте новый проект Laravel и базу данных для него. Самым утомительным моментом будет аутентификация. Нам нужно сделать аутентификацию по протоколу OAuth2 и для этого мы будем использовать Laravel Passport. Пожалуйста, самостоятельно прочитайте про OAuth2, если вы не имеете представления о том, что это такое - на этом я тоже не собираюсь заострять внимание. Итак, давай установим упомянутый пакет:

composer require laravel/passport

Внутри пакета находится несколько миграций для хранения клиентов, токенов доступа, и т.п. Поэтому нам нужно вызвать эти миграции:

php artisan migrate

Затем выполните следующую команду:

php artisan passport:install

Обратите внимание на то, что эта команда возвращает id клиента (client ID) и секрет (client secret) для двух клиентов. Мы будем использовать Password grant client, который позволяет пользователям авторизироваться используя логин (или email) и пароль. Скопируйте client ID и client secret куда-нибудь - они нам понадобятся позже. Хотя, вы всегда можете подсмотреть эти значения в БД в таблице oauth_clients.

Вызовите метод Passport::routes() внутри метода boot сервис-провайдера AuthServiceProvider:

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

    Passport::routes(function ($router) {
        $router->forAccessTokens();
    });
}
...

Этот метод регистрирует маршруты Laravel Passport. Мы зарегистрируем только маршруты для авторизации по логину/паролю. Далее добавьте trait Laravel\Passport\HasApiTokens в модель пользователя App\User. Это не только необходимо для работы паспорта, но также добавляет некоторые полезные методы для модели.

...
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
...

Наконец, откройте файл config/auth.php и замените driver для api на passport.

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

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

Аутентификация

Теперь нам нужно добавить собственные маршруты и контроллеры для аутентификации пользователей. Мы сделаем регистрацию, вход и выход пользователей. Не будем усложнять структуру проекта и поместим все в один контроллер. Давайте создадим его:

php artisan make:controller 'API\AuthController'

Откройте файл routes/api.php и удалите оттуда маршрут по-умолчанию. Вместо него добавьте:

Route::post('/register', 'API\AuthController@register');
Route::post('/login', 'API\AuthController@login');

Route::middleware('auth:api')->group(function () {
    Route::post('/logout', 'API\AuthController@logout');
});

Заметьте, что маршрут "/logout" находится под защитой middleware auth:api, что означает, что только у авторизированных пользователей будет доступ к нему. Теперь займемся методом регистрации register в нашем котроллере. Опять же, мы не будем сильно заморачиваться и опустим валидацию данных.

public function register()
{
    User::create([
        'name' => request('name'),
        'email' => request('email'),
        'password' => bcrypt(request('password'))
    ]);

    return response()->json(['status' => 201]);
}

Рекомендую использовать приложение Postman для тестирования API. Это бесплатный инструмент, который позволяет посылать запросы на сервер и получать ответы, имитируя работу клиента API. Установите программу на свой компьютер, запустите сервер разработки php artisan serve и отправьте POST запрос на адрес http://localhost:8000/api/register. Заполните значения для полей "name", "email" и "password" во вкладке Body и нажмите Send. Вы должны получить следующий ответ в формате JSON:

{"status":201}

Теперь перейдем к более сложной части - входу. У Laravel Passport есть маршрут для аутентификации пользователей. На него нам нужно отправить поля grant_type, client_id, client_secret, username и password, и в ответе мы получим 2 токена - токен доступа, который используется для совершения запросов к API, и токен обновления, который используется для получение свежего токена доступа если текущий истек. Срок работы токена доступа по-умолчанию равен одному году, поэтому мы не будем реализовывать его обновление.

Можем ли мы делать запросы на данный маршрут напрямую? Можем, но это небезопасно, так как для этого нам придется хранить client_id и client_secret внутри кода JavaScript/Vue. Вместо этого мы будем добавлять эти два значения к отправляемым данным в методе login нашего контроллера, а затем перенаправлять все это на маршрут паспорта.

public function login()
{
    $client = DB::table('oauth_clients')
        ->where('password_client', true)
        ->first();

    $data = [
        'grant_type' => 'password',
        'client_id' => $client->id,
        'client_secret' => $client->secret,
        'username' => request('username'),
        'password' => request('password'),
    ];

    $request = Request::create('/oauth/token', 'POST', $data);

    return app()->handle($request);
}

Мы имитируем отправку запроса Laravel, чтобы сделать внутренний запрос к API. Теперь сделайте POST запрос на http://localhost:8000/api/login в Postman с полями username (в качестве значения укажите email) и password. Вы должны получить ответ следующего вида:

{
    "token_type": "Bearer",
    "expires_in": 31536000,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijc0MDc1YzhlMzkzZDE5YTIwZDk5YTc5MjlkZGI5MjlkYjQzN2RjMDNhNjY4ZmM4ZTUyZmI3ZjRiMDY4ZTBjYmJkYzY4YWI3MDkyYWMyOTk4In0.eyJhdWQiOiIyIiwianRpIjoiNzQwNzVjOGUzOTNkMTlhMjBkOTlhNzkyOWRkYjkyOWRiNDM3ZGMwM2E2NjhmYzhlNTJmYjdmNGIwNjhlMGNiYmRjNjhhYjcwOTJhYzI5OTgiLCJpYXQiOjE1MTgwOTc0MzQsIm5iZiI6MTUxODA5NzQzNCwiZXhwIjoxNTQ5NjMzNDM0LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.cxa6WNUWKy81uXJSMtx7c4zFEneE9rhYKklH_gXG2qvLSeyoly6zg923tzTkbAUASZiXzvU9l5MOs0qzrK3jPLFJmIz6P0uCEcSCQL9oM_6lOvoLOL6aaJyRIur2MrO_jJ1O5KX91HGOuUsbHiwqsxjjaV64AN4HUYObhgfOBWOT-693Xj1QL-soTSRLdCefPn-R3aT3XkgBd06RHJ2QW3qVWHFgfPiEsPG5EwRZdNWR8JEmCd_KwSDyEcdBrlmEe77JBwexI_YE2bcWbJYQEMUU7WmKQn0ELaLzm5iReRiMsRTITWx7s0uSn8cjwofO0f0s7TFou7hOIw4uuRBOYo2VyQHMhbpzQxE9_e6hef5PXN3J0sKZR_d8TGvwEekwUGpj9xxbZjF5gsPfSGl1f6Bmumamhosx-nplhE-HAPr4FCgCJTQhLnpjzev-P2Xhe_qDOo61WAuIcs1yPYb8TjDFy9wiOLeifRIr9-FEyn2AWxxPFZMcLOJoDBlTiLWOkWm21mKHRPwDdWi7-mlMUnJvmjeGJp2s64hrQES6AOgtjG4NSye9ab_jU4RwPA2t0coKDkyUSU7RQFEKOv5RcB620vEgSSIuz4W_LFZ4tbCDcCNqda2FqcJxEeB6JOVifGabq3-_ZKITlcGZ5AX-6ECvpGczd9WZnGz2pVScJyQ",
    "refresh_token": "def502009b8b24b62eacb618172846eee036b135eda5ba8516af270c40d8bf4dc25043277c4ed91636981c841a5558d821b34e4c6620d20b78b62859e66f7dfe478d7c603cb747527e6cfde6a3ae0eec8fb2459714ba695f787cdf485cca1212779b17f0aaa729e6e0897633dc45eeebdd065a3ee477be28c47334c5f0fa895ad7f85f1d2ebabcec1e9536afc0713073d1c650a57c06c1d3a6a54ff8702f9afd5d8ae6da12562184ffa7e6a27fb4f6b054c25f396b97e0588059f2d18ef1cf119ec1994b64fe4be42860fb8ae52b490752079e2edee22c73838c6dfa4645cb1f4ae736f363e5111c447c093939713595d24695b963b4da2d5c75c6259e30aaade7034fc718d8cf2d9eeb0fe11524b527b2b0ac7175c6e9a6a41150327fbdc6c472c01149a5bff2e66cf9bff0f5e80a4fac1927c70b191bea4b874000fb94ea3b2bea878d93a0ed40d40cfd078532862baeac0e84781a4e973b3c2a7afeadc504eb"
}

Давайте попробуем сделать запрос с токеном доступа (access_token). Но сначала давайте добавим метод logout в наш контроллер. Так как этот метод доступен только авторизированным пользователям - он идеально подходит для проверки работы токена.

public function logout()
{
    return 'Protected route';
}

Теперь зайдите во вкладку Headers в Postman и добавьте ключ Authorization со значением Bearer %token%, где %token% - это значение токена access_token из ответа, который мы получили ранее. Сделайте запрос на http://localhost:8000/api/logout и вы должны увидеть текст "Protected route" в ответе. Попробуйте убрать заголовок Authorization из запроса и ответ будет с ошибкой.

Наконец, давайте напишем метод logout по-нормальному. В Laravel Passport есть метод, который делает любой токен доступа недействительным. При этом, нам нужно будет вручную обновить БД, чтобы сделать недействительным токен обновления.

public function logout()
{
    $accessToken = auth()->user()->token();

    $refreshToken = DB::table('oauth_refresh_tokens')
        ->where('access_token_id', $accessToken->id)
        ->update([
            'revoked' => true
        ]);

    $accessToken->revoke();

    return response()->json(['status' => 200]);
}

Сделайте запрос на маршрут logout еще раз и вы должны получить следующий ответ:

{"status":200}

Сделайте повторный запрос и Laravel попытается перенаправить вас на страницу входа - это значит, что мы больше не авторизированы и токен больше не действителен. Поздравляю! Начнем работать над веб-клиентом в следующей части урока.