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



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

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

Примечание
Сразу хотелось бы отметить, что я рассматриваю достаточно упрощенную реализацию — в реальном проекте стоило бы использовать валидацию рекордов, выделить обработку запросов в отдельный контроллер, а не писать прямо в роуте, шифровать токен при сохранении в Unity, а также другое такого плана — статья не ставит себе цели покрывать все такие моменты, поэтому они были мною опущены. Я рассматриваю пошагово, для новичков, но всё-таки предполагаю, что у читателя есть базовые навыки работы с локальным сервером и Unity. Если что-то слишком или недостаточно подробно — пишите в комментариях! Это моя первая техническая статья и я открыт для предложений.

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


  1. Создаём новый проект 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
      Для версий Laravel ниже 5.5 в файле config/app.php необходимо было зарегистрировать соответствующий сервис-провайдер. Для этого в массив providers добавляем строку

      Laravel\Passport\PassportServiceProvider::class,

      Если вы устанавливаете 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 с сообщением, что рекорд добавлен.



    Примечание
    Мы добавляем рекорд как есть, подразумевается, что поле score целочисленное и неотрицательное. Думаю, не стоит останавливаться на том, что в реальном проекте это нужно проверять, тем более что в Laravel это можно было бы сделать в специальном классе запроса для того же рекорда. Кроме того, стоило бы вынести добавление в класс-контроллер (при необходимости выделив запись в отдельный класс репозитория), чтобы избавиться от дублирования кода.

    Для отображения добавленных рекордов на сайте напишем роут в 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>{{ $record->user ? $record->user->name : 'Аноним' }}</td>
                                    <td>{{ $record->score }}</td>
                                    <td>{{ $record->created_at->diffForHumans() }}</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.

    Готовый проект можно скачать на гитхабе.
Поделиться публикацией
Комментарии 2

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

Самое читаемое