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

 

Клікаємо на play і отримуємо результат в такому форматі:

{
  "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
    }
  }
}

 

Відмінно! Мета досягнута. Звісно, слід перевірити і інші мутації і запити, але не думаю, що слід описувати ці дії в статті - адже це звичайна рутина.

На сьогодні все. Успіхів!