Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 1

В конце прошлого лета я задумался над простым способом авторизации пользователей форума в мобильном приложении. Как раз в это время вышла версия Laravel 5.3 вместе с пакетом Laravel Passport, где подобное предлагалось из коробки. Раньше я не работал с OAuth 2, так что начал не спеша разбираться. Решил испытать механизм на крысах, в небольшой игре на Unity про Крысу на Стене. Сама игра — простейший раннер, но механизм авторизации может представлять некоторый интерес, если ранее не сталкивался с этим. Я пользовался официальной документацией и статьей про Passport. На хабре подходящей статьи до сих пор не появилось, поэтому решил сам скомпоновать материал, реализовав для интереса добавление рекордов и базовое взаимодействие с клиентом на Unity. Ввиду моей неторопливости это растянулось почти на год, так что сейчас в примерах используются уже Laravel 5.5 и Unity 2017.1.

В первой части статьи разберёмся, как с помощью токена авторизации добавить рекорд пользователя на сайт.

 
Примечание

Установка Laravel и Laravel Passport

Создаём новый проект Laravel, заводим БД под него.

  1. Устанавливаем глобально вспомогательный установщик Laravel через консоль при помощи Composer
     
    composer global require "laravel/installer"
  2. Для создания проекта через установщик достаточно набрать команду в формате laravel new <название проекта>

     
    laravel new ratwall-laravel

    Дальнейшие команды выполняем из созданной директории. Заходим туда
     
    cd ratwall-laravel
  3. В файле .env прописываем данные для подключения к базе данных. В этом гайде мы будем работать с SQLite, пропишем

     
    DB_CONNECTION=sqlite
    DB_DATABASE=/полный/путь/к/папке/проекта/database/database.sqlite

    После этого создадим соответствующий пустой файл (командой touch database/database.sqlite из консоли или, в случае Windows, из любого редактора файл database.sqlite в директории database)
  4. Теперь можно запустить проект локально командой
     
    php artisan serve

    Он будет доступен по адресу http://127.0.0.1:8000/
  5. Для нашего проекта будет достаточно базовой системы аутентификации на сайте, поэтому запускаем команду
     
    php artisan make:auth
  6. Добавляем зависимости Laravel Passport через Сomposer.
     
    composer require laravel/passport
  7.  
    Для версий Laravel ниже 5.5
  8. Базовая система аутентификации создала свои миграции. Импортируем в базу данных вместе с миграциями Passport.
     

    php artisan migrate
  9. Теперь можно запустить установщик Passport.
     
    php artisan passport:install

    Установщик создаст пару ключей, в дальнейшем мы будем работать с ключом типа Password grant client, так что можно сразу записать ключ для Client ID = 2.
  10. Добавим в модель пользователя App\User трейт Laravel\Passport\HasApiTokens с методами Passport. Класс будет выглядеть так:
     
    <?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;
        // ...
        // оригинальный код
        // ...
    }
  11. Следующий шаг — назначение роутов. Здесь это делается добавлением метода Passport::routes в загрузочный метод (boot) сервис-провайдера
    App\Providers\AuthServiceProvider. Сервис-провайдер будет выглядеть следующим образом:
     
    <?php
    
    namespace App\Providers;
    
    use Illuminate\Support\Facades\Gate;
    use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
    use Laravel\Passport\Passport;
    
    class AuthServiceProvider extends ServiceProvider
    {
        // ...
        // оригинальный код
        // ...
        public function boot()
        {
            $this->registerPolicies();
    
            Passport::routes();
        }
    }
  12. Наконец, в файле config/auth.php устанавливаем драйвер у гарда api как passport.
     
        'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
    
            'api' => [
                'driver' => 'passport',
                'provider' => 'users',
            ],
        ],

Password Grant Token


Есть несколько способов авторизации через Passport. Мы рассмотрим простейший способ авторизации через логин-пароль, посредством Password Grant Token. Этот способ позволяет пользователю заполнить форму авторизации прямо в вашем приложении. При установке Laravel Passport один клиент для этого способа авторизации уже был создан (Password grand client, Client ID = 2), можно использовать его. Для создании нового нужно запустить
 

php artisan passport:client --password


Введите название для клиента, чтобы завершить создание. Команда выведет Client ID и Client Secret. Запомните их, они понадобятся и в этой статье, и в следующей, при формировании запроса от клиента игры.
 

Модель рекордов


Создадим модель для будущих рекордов. Ключ -m означает, что будет также создана соответствующая миграция. При добавлении ключа -c также будет создан контроллер, но в нашем упрощенном случае это не нужно.
 

php artisan make:model Record -m


После создании модели нужно подготовить миграцию. Отредактируем файл database/migrations/<дата>_create_records_table.php. По-умолчанию у миграции прописано создание поля id (для первичного ключа) и сразу два поля для сохранения времени создания и редактирования записи (timestaps). Для простейшего хранения данных о рекордах добавим два числовых поля — score (значение рекорда) и user_id (идентификатор пользователя, поставившего рекорд). После этого миграция будет выглядеть следующим образом:
 

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateRecordsTable extends Migration
{
    public function up()
    {
        Schema::create('records', function (Blueprint $table) {
            $table->increments('id');
            // Значение рекорда - только положительное
            $table->integer('score')->unsigned();
            // Идентификатор пользователя - поле может быть пустым
            $table->integer('user_id')->unsigned()->nullable();
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('records');
    }
}


Импортируем в БД с помощью команды
 

php artisan migrate


Отредактируем саму модель App\Record. Добавим метод user() для связи с моделью пользователя по полю user_id.
 

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Record extends Model
{
  // Пользователь
  public function user()
  {
    return $this->belongsTo(User::class);
  }
}

 

Контроллер рекордов


Для добавления рекорда в базу создадим два роута для API, практически одинаковых, но один из них предназначен для добавления рекорда анонимным пользователем, а другой — авторизированным. В этом туториале мы не будем писать отдельный контроллер для рекордов, всю обработку вынесем в анонимную функцию роута. Для этого в файл routes/api.php добавим:
 

Route::post('/anonymrecord', function (Request $request) {
    // Создаем новую запись рекорда
    $record = new \App\Record();
    // Добавляем результат
    $record->score = $request->get('score');
    // Сохраняем запись
    $record->save();
    // Возвращаем сообщение
    return response()->json([
            'message' => 'Рекорд добавлен!',
        ], 201);
});

Route::middleware('auth:api')->post('/record', function (Request $request) {
    // Создаем новую запись рекорда
    $record = new \App\Record();
    // Добавляем результат
    $record->score = $request->get('score');
    // Если запись добавил авторизированный пользователь, указываем его
    $record->user_id = \Auth::id();
    // Сохраняем запись
    $record->save();
    // Возвращаем сообщение
    return response()->json([
            'message' => 'Пользователь '. \Auth::user()->name .' добавил рекорд!',
        ], 201);
});


Здесь мы передаем рекорд в POST-запросе, добавляем его в базу и возвращаем сообщение об успехе. Мы добавили два практически идентичных роута — первый для анонимного добавления, второй для добавления рекорда авторизированным пользователем. Авторизированный пользователь может воспользоваться и анонимным роутом, но в нем не будет корректно обрабатываться информация о пользователе, поэтому в качестве варианта решения мы и разделили роуты. При вызове второго роута анонимным пользователем вас будет перенаправлять на форму авторизации.

В этом файле уже был один роут — user, для получения информации об авторизированном пользователе. Мы ещё вернемся к нему позже.

Можно проверить работоспособность запроса для добавления анонимного рекорда с помощью http-клиента типа Postman (в дальнейшем примеры именно с ним), передав POST-запрос с заданным числовым значением score в форме по адресу http://127.0.0.1:8000/api/anonymrecord. В ответ мы получим json с сообщением, что рекорд добавлен.

 
Примечание

Для отображения добавленных рекордов на сайте напишем роут в routes/web.php с запросом всех рекордов из базы. Он использует шаблон records.blade.php, которое мы создадим в следующем шаге.
 

Route::get('/records', function () {
    $records = \App\Record::all();
    return view('records', compact('records'));
});

 

Представление рекордов


Создадим шаблон resources/views/records.blade.php для отображения таблицы рекордов.
 

@extends('layouts.app')
@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Рекорды</div>
                <div class="panel-body">
                    <table class="table table-striped table-hover">
                        <tr>
                            <th>Пользователь</th>
                            <th>Рекорд</th>
                            <th>Дата</th>
                        </tr>
                        @foreach ($records as $record)
                            <tr>
                                <td></td>
                                <td></td>
                                <td></td>
                            </tr>
                        @endforeach
                    </table>     
                </div>
            </div>
        </div>
    </div>
</div>
@endsection


Если рекорд добавил авторизированный пользователь, выводим его имя, иначе — Аноним.
Теперь по адресу http://127.0.0.1:8000/records нам доступна таблица рекордов — пустая или с теми записями, что мы добавили через Postman.

Эти рекорды добавлялись от Анонима, т.к. мы обращались к роуту анонимного добавления, а не к роуту авторизированного пользователя с токеном авторизации через Postman. Займемся вторым случаем.
 

Авторизация через OAuth 2


Зарегистрируемся на сайте, используя стандартный механизм Laravel, перейдя по адресу http://127.0.0.1:8000/register. Для примера я указал e-mail habr@habrahabr.ru, пароль habrahabr, имя Habr.

Для того, чтобы получить токен авторизации через Password Grant Token, нужно отправить POST-запрос на адрес http://127.0.0.1:8000/oauth/token. В тело запроса нужно добавить следующие данные:

  • grant_type — указываем тип password, т.к. мы используем Password Grant Token;
  • client_id — идентификатор Client ID из раздела Password Grant Token;
  • client_secret — ключ Client Secret, из раздела Password Grant Token;
  • username — в качестве логина для авторизации в базовом функционале Laravel используется e-mail;
  • password — пароль,
  • scope — области применения токена (для задания разрешений), оставим *, т.е. все.


Отправим такой запрос через Postman. Если всё заполнено правильно, в ответ мы получим токен обновления, токен авторизации, время действия токена в секундах и тип токена — Bearer. Нам нужен токен авторизации — Access Token.

Проверим токен в действии, получив по нему информации об авторизированном пользователе. Выполним GET-запрос через Postman по адресу http://127.0.0.1:8000/api/user. При этом в раздел Headers нового запроса нужно добавить ключ Authorization со значением Bearer <токен авторизации> (то есть тип токена, пробел, сам токен). В результате мы получим информацию о пользователе, которому соответствует этот токен авторизации.

Добавление рекорда авторизированным пользователем


Теперь можно выполнить POST-запрос для добавления рекорда авторизированным пользователем через Postman. Заполним Headers нового POST-запроса аналогично последнему, в качестве адреса используем http://127.0.0.1:8000/api/record. В ответном сообщении теперь отображается имя пользователя, которое соответствует переданному токену.

Если зайти на страницу рекордов, можно увидеть, что у последнего добавленного рекорда стоит имя пользователя.

В следующей части будем работать с нашим небольшим API уже из Unity.

Готовый проект можно скачать на гитхабе.