REST API аутентификация с использованием Laravel Passport
Установка и конфигурация
Устанавливаем пакеты:
# composer require paragonie/random_compat:2.*
# composer require laravel/passport=~4.0
Выполняем миграции:
# php artisan migrate
должно быть примерно так:
Создаём ключи шифрования, которые необходимы для генерирования безопасных токенов:
# php artisan passport:install
должно быть примерно так:
после команды создадутся клиенты «personal access» и «password grant», которые будут использоваться при генерации токенов.
Добавить трейт HasApiToken в модель User:
файл: app/User.php
<?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;
...
}
Прописать вызов роутов в методе boot() провайдера AuthServiceProvider (app/Providers/AuthServiceProvider.php):
<?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();
}
}
в файле конфигурации config/auth.php в качестве параметра драйвера для защиты api аутентификации установим значение passport:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
очистить кеш конфигурации
# php artisan config:clear
Роуты и контроллеры
RegisterController и RegisterFormRequest
Валидацию вынести из контроллера в отдельный класс. Для этого выполним:
# php artisan make:request Api/Auth/RegisterFormRequest
Открыть созданный файл app/Http/Requests/Api/Auth/RegisterFormRequest.php и вставить следующий код:
<?php
namespace App\Http\Requests\Api\Auth;
use Illuminate\Foundation\Http\FormRequest;
class RegisterFormRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:6', 'confirmed'],
];
}
}
Чтобы создать контроллер с единственным методом следует использовать флаг -i (сокращение от —invokable):
# php artisan make:controller -i Api/Auth/RegisterController
Вместо обычного Request внедряем созданный ранее RegisterFormRequest, после чего остаётся только создать пользователя и вернуть ответ. Не будем возвращать токен, а лишь укажем, что пользователь успешно зарегистрирован, и теперь может войти в систему, используя свой почтовый адрес и пароль:
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Auth\RegisterFormRequest;
use App\User;
class RegisterController extends Controller
{
public function __invoke(RegisterFormRequest $request)
{
$user = User::create(array_merge(
$request->only('name', 'email'),
['password' => bcrypt($request->password)],
));
return response()->json([
'message' => 'You were successfully registered. Use your email and password to sign in.'
], 200);
}
}
LoginController
Аналогичным образом создаём контроллер для входа:
# php artisan make:controller -i Api/Auth/LoginController
Берём из запроса email и пароль и пробуем войти. Если, данные не верны — отправим соответствующее сообщение и код ошибки:
$credentials = $request->only('email', 'password');
if (!Auth::attempt($credentials)) {
return response()->json([
'message' => 'You cannot sign with those credentials',
'errors' => 'Unauthorised'
], 401);
}
Если же всё прошло «как надо», сгенерируем токен:
$token = Auth::user()->createToken(config('app.name'));
в переменной $token находится не непосредственно jwt-токен, а объект, который в моём случае в Postman выглядит так:
{
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijk2YWYzNDBlZjVlNTMxZmY0MTc4NmJkM2NiN2QwZjk4Yzh...",
"token": {
"id": "96af340ef5e531ff41786bd3cb7d0f98c8c323b30c1a4857a027aa064b1c40adb50a5a0e6668ae46",
"user_id": 3,
"client_id": 1,
"name": "Test App",
"scopes": [],
"revoked": false,
"created_at": "2019-01-15 19:49:37",
"updated_at": "2019-01-15 19:49:37",
"expires_at": "2020-01-15 19:49:37"
}
}
Теперь, когда объект токена перед глазами, станут понятны дальнейшие действия. Будем исходить из предположения, что в форме логина мог быть отмечен чекбокс «Запомнить меня». Если это так, установим срок жизни токена в один месяц, если нет — один день, и затем сохраним изменения:
$token->token->expires_at = $request->remember_me ?
Carbon::now()->addMonth() :
Carbon::now()->addDay();
$token->token->save();
Полный код контроллера:
<?php
namespace App\Http\Controllers\Api\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class LoginController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
$credentials = $request->only('email', 'password');
if (!Auth::attempt($credentials)) {
return response()->json([
'message' => 'You cannot sign with those credentials',
'errors' => 'Unauthorised'
], 401);
}
$token = Auth::user()->createToken(config('app.name'));
$token->token->expires_at = $request->remember_me ?
Carbon::now()->addMonth() :
Carbon::now()->addDay();
$token->token->save();
return response()->json([
'token_type' => 'Bearer',
'token' => $token->accessToken,
'expires_at' => Carbon::parse($token->token->expires_at)->toDateTimeString()
], 200);
}
}
LogoutController
«отзываем» токен:
<?php
namespace App\Http\Controllers\Api\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class LogoutController extends Controller
{
public function __invoke(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'You are successfully logged out',
]);
}
}
Роуты
Внимание: если Вы определите маршруты перед созданием Single action controllers, в процессе создания контроллеров в консоли будет вылетать ошибка с требованием указать метод в роутах. Поэтому рекомендую придерживаться последовательности, описанной в этой статье (либо пойти стандартным путём и разместить все методы в AuthController).
Открыть файл routes/api.php и прописать маршруты:
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
Route::group(['namespace' => 'Api'], function () {
Route::group(['namespace' => 'Auth'], function () {
Route::post('register', 'RegisterController');
Route::post('login', 'LoginController');
Route::post('logout', 'LogoutController')->middleware('auth:api');
});
});
Небольшое пояснение: следует учитывать, что помимо аутентификации в каталоге Api будут располагаться и другие контроллеры или группы контроллеров, поэтому логичнее в группу с общим неймспейсом вложить другие подгруппы (например, categories, products и т.д.)