Laravel + Passport + GraphQL: аутентификация

Laravel + Passport + GraphQL: аутентификация

Рассмотрим, как реализовать аутентификацию Laravel в связке с Passport и GraphQL.

ОС: Ubuntu 18.04. Версия фреймворка: 5.8. PHP: 7.3. MySQL: 5.7.26 

Создадим новый проект (предполагается, что установлен инсталлятор laravel. Если это не так, смотрите документацию):

laravel new lara-graphql

 

На сей раз обойдёмся без Homestead. Заходим в mysql:

mysql -u root

 

И создаём базу данных:

create database laravel_graphql;

 

На всякий случай проверим всё ли прошло нормально:

show databases;

 

Выходим командой exit. Затем открываем файл .env и прописываем настройки базы:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_graphql
DB_USERNAME=root
DB_PASSWORD=

 

Чтобы было интересней, немного изменим миграцию пользователей:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('username');
            $table->string('email')->unique();
            $table->string('first_name');
            $table->string('last_name');
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

 

В соответствии с миграцией сразу же изменим фабрику:

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User;
use Illuminate\Support\Str;
use Faker\Generator as Faker;

$factory->define(User::class, function (Faker $faker) {
    return [
        'username' => $faker->userName,
        'email' => $faker->unique()->safeEmail,
        'first_name' => $faker->firstName,
        'last_name' => $faker->lastName,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

 

Зайдём в тинкер:

php artisan tinker

 

И создадим нескольких (5) пользователей:

>>> factory(App\User::class, 5)->create();

 

Далее ставим пакет laravel passport (полная документация здесь):

composer require laravel/passport

 

Запускаем миграции:

php artisan migrate

 

И выполним команду, которая создаст ключи, необходимые для генерации токенов:

php artisan passport:install

 

В результате мы получим что-то вроде этого:

Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client secret: f2ymreiKBnaTai7JEGmZLQ843h2FYOjFzKTPylw4
Password grant client created successfully.
Client ID: 2
Client secret: g28LlYBp09o77OrhNfhJvgwZzDhBIUi24qQyO5aa

 

Нас интересует password grant. Скопируем client secret из последней строки и вставим в файл .env такие строки (они нам пригодятся в дальнейшем при работе с GraphQL):

PASSPORT_CLIENT_ID=2
PASSPORT_CLIENT_SECRET=g28LlYBp09o77OrhNfhJvgwZzDhBIUi24qQyO5aa

 

Добавляем в модель User трейт HasApiToken:

<?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;
    ....
}

 

Затем в 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
{
    /**
     * 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(function ($router) {
           $router->forAccessTokens();
        });
    }
}

 

Внесём необходимые изменения в config/auth.php:

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

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

 

Переходим к GraphQL. В первую очередь установим lighthouse (это пакет, который позволяет обслуживать GraphQL эндпойнт в приложении Laravel):

composer require nuwave/lighthouse

 

Данный пакет включает в себя схему по умолчанию, опубликуем её:

php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema

 

А также конфигурацию:

php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=config

 

Для запросов нам также понадобится веб-нитерфейс, потому также поставим GraphQL Playground (такой себе аналог Postman):

composer require mll-lab/laravel-graphql-playground

 

И добавим фасад в config/app.php:

'aliases' => [
    // ...
    'GraphQL' => Nuwave\Lighthouse\Support\Facades\GraphQLFacade::class,
]

 

Кроме этого, понадобится пакет для работы с паспортом и установленным ранее lighthouse:

composer require joselfonseca/lighthouse-graphql-passport-auth

 

По умолчанию схема определяется внутри пакета. Однако, если мы захотим её переопределить, нам следует её опубликовать (будет также опубликована и конфигурация - файл lighthouse-graphql-passport.php в директории config):

php artisan vendor:publish --provider="Joselfonseca\LighthouseGraphQLPassport\Providers\LighthouseGraphQLPassportServiceProvider"

 

Откроем вышеупомянутый файл конфигурации пакета (lighthouse-graphql-passport.php) и добавим путь к схеме:

'schema' => base_path('graphql/auth.graphql')

 

Важно: если мы посмотрим на содержимое файла graphql/auth.graphql, то увидим в конце файла:

extend type Mutation {
    ...
}

 

а это означает, что мы расширяем мутации, которые определены в graphql/schema.graphql. Но на данный момент там никаких мутаций нет, а потому будем получать ошибку:

{
  "errors": [
    {
      "message": "Schema is not configured for mutations.",
      "extensions": {
        "category": "graphql"
      },
      "locations": [
        {
          "line": 1,
          "column": 1
        }
      ]
    }
  ]
}

 

и будем ломать голову почему так происходит. Чтобы этого не случилось, допишем в graphql/schema.graphql какую-нибудь мутацию, например:

input RegisterData {
    email: String
    username: String
    first_name: String
    last_name: String
    password: String
}

type Mutation {
    register(data: RegisterData): User @create
}

 

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

Что ж  - вроде всё на месте. Запускаем встроенный сервер:

php artisan serve

 

Затем в браузере открываем страницу localhost:8000/graphql-playground. Скопируем почту одного из созданных ранее пользователей (пароль у всех одинаковый - password), и в левой части плейграунда добавим мутацию:

mutation {
  login(data: { username: "jschiller@example.net", password: "password" }) {
  	access_token
    refresh_token
    expires_in
  }
}

 

Кликаем на плей и получаем результат в таком формате:

{
  "data": {
    "login": {
      "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjkwODRhYTNjOGJmOTgzMGZhYWE3Y2JjNmFhZGFjZDdlNWM5N2ZmZjRkNzIwODJiOWEwOWYzY2QzYzhhYzE1OWMwYmYyOTdhODU4MTkxMDNiIn0.eyJhdWQiOiIyIiwianRpIjoiOTA4NGFhM2M4YmY5ODMwZmFhYTdjYmM2YWFkYWNkN2U1Yzk3ZmZmNGQ3MjA4MmI5YTA5ZjNjZDNjOGFjMTU5YzBiZjI5N2E4NTgxOTEwM2IiLCJpYXQiOjE1NTg0NzczNjgsIm5iZiI6MTU1ODQ3NzM2OCwiZXhwIjoxNTkwMDk5NzY4LCJzdWIiOiI1Iiwic2NvcGVzIjpbXX0.Mf4Vfbab3lgxPlTH7CEaboaizW5J8t6DGaWv7HAEnwHkxTFy1neaziyQRqNplbx5hU6Azeo7cFyy3iQv2w-LJSfE0nznjL7-ALVjqnyVro_k_3HYFjvZBSSiowYcdOzIlgdv04dO2PFS_qn8Ap6rQQEbEHfewW9PbEH7KL3QYqPowKsgtXmzSlepmdd8lRDVlEp1oIp3cP5EqZw1ECNnqC_KjHnUnwFwCmi4u_BFLzRBMFltHI1xPs7rim4_IwIB8ShN7a5HHAiydjV1JFhnIDJCyVCVqEMQcLNFgI7XBwBvoKcFYmZFm8Kv_If-RUYVrz6--CMK24QEnNCMz1oqR0WE24WwZkZ87FDY2hVEm1HVtI6BS8LGMguxLNMGainlgzq1jnSgpOfw9BPwO_bfr6Az3-nKpwx-4hFUCZoz7N8wbhHkmCV3bbcYN5jYVDUKYDlltaFEQgZisZkTzHgukYetDJ1nWZJJQl8V73AjrCTrAUSfkVSqpRb77GEicFwmeQ_HqR20v3E8ahVMCC3p5g4LyKVps5DNVPOKIh2oH5kE1q54jRRV2qCVG7VDjIJyiERzafxyfWce0dkm7UGwQyToFn_3hP2AyHQ3CIGhilCyJk-O5yUpNcReuNtAwiuXWNhXzn5n6CKJLJrxGJ4Mr8XaXM-jzKKk1Tb5qKqs814",
      "refresh_token": "def50200971a7f4aeadf6f782de91f6263a0010531a339c21b30bb8e99bb60bda0407a93fb11efc3bfa094c0f8b0c0591460cb9825d6fb825b309155e4960c9b18ed2a654304fa06c5c9aa48e14c637f842f476b3dd23defb27e5f777f540f38d764899a92c4aec1d8e494e07830ed43fbe3be3320a832dd36042603f6280a04e8d7bed53e48a343d6538b77907dd46ad6666527a2472b6446a0e92558ce201e4787efc001bea1fd1d31ee80284dab1707f07783cfed5c2a6fc9a6e5d01f34a320c7fb403ab07be69f5b502f8b583f1d2ba1326e9278b62f7e7d7ab23ac7ef1107cba0ee20061829f0bd5234e5341562e1ecffc6d5a441bdc87e7f85a38a484812490d795e0bdbb1a2d733930a09fd0375df4114186e07bc2bba65fd9c276b0f3ca299c8b0bead02f754e273d0b77ef29889c296956a44417b1cbd752453544da47abfd5e94cae887010b648faf2140c5bbffac27b6805a3e4520e581af9e06f87",
      "expires_in": 31622400
    }
  }
}

 

Отлично! Цель достигнута. Естественно, следует проверить и остальные мутации и запросы, но не думаю, что следует описывать эти действия в статье - ведь это обычная рутина.

На сегодня всё. Успехов!