Laravel Passport
- Введение
- Passport или Sanctum?
- Установка
- Развертывание Passport
- Обновление Passport
- Настройка
- Срок жизни токена
- Переопределение моделей по умолчанию
- Переопределение маршрутов
- Предоставление кода авторизации
- Управление клиентами
- Запрос токенов
- Управление токенами
- Обновление токенов
- Отзыв токенов
- Удаление токенов
- Предоставление кода авторизации с помощью PKCE
- Создание клиента
- Запрос токенов
- Предоставление авторизации устройству
- Создание клиента предоставления авторизации устройства
- Запрос токенов
- Предоставление пароля
- Создание токенов
- Запрос токенов
- Запрос токена для всех областей
- Настройка пользовательского провайдера
- Настройка поля имени пользователя
- Настройка проверки пароля пользователя
- Неявное разрешение
- Разрешение учетных данных
- Получение токенов
- Токены персонального доступа
- Создание токенов персонального доступа
- Настройка поставщика услуг для пользователей
- Управление токенами персонального доступа
- Защита маршрутов
- Защита маршрутов через посредников
- Защита маршрутов через передачу токена
- Области токенов
- Определение областей
- Области по-умолчанию
- Назначение областей токенам
- Проверка областей
- Аутентификация SPA
- События
- Тестирование
Введение
Laravel Passport обеспечивает полную реализацию сервера OAuth2 для вашего приложения Laravel за считанные минуты. Passport построен на основе League OAuth2, который поддерживается Энди Миллингтоном (Andy Millington) и Саймоном Хэмпом (Simon Hamp).
В этой документации предполагается, что вы уже знакомы с OAuth2. Если вы ничего не знаете о OAuth2, перед продолжением ознакомьтесь с общей терминологией и функциями OAuth2.
Passport или Sanctum?
Прежде чем начать, вы можете определиться, будет ли ваше приложение лучше обслуживаться через Laravel Passport или Laravel Sanctum. Если вашему приложению необходима поддержка OAuth2, то следует использовать Laravel Passport.
Однако, если вы пытаетесь аутентифицировать одностраничное приложение, мобильное приложение или выдавать токены API, вам следует использовать Laravel Sanctum. Laravel Sanctum не поддерживает OAuth2; однако он обеспечивает гораздо более простой опыт разработки аутентификации API.
Установка
Вы можете установить Laravel Passport с помощью Artisan-команды install:api
:
php artisan install:api --passport
Эта команда опубликует и запустит миграцию базы данных для создания таблиц, необходимых вашему приложению для хранения клиентов OAuth2 и токенов доступа. Команда также создаст ключи шифрования, необходимые для создания токенов безопасного доступа.
После выполнения команды install:api
добавьте трейт Laravel\Passport\HasApiTokens
и интерфейс Laravel\Passport\Contracts\OAuthenticatable
в модель App\Models\User
. Этот трейт предоставит вашей модели несколько вспомогательных методов, которые позволят вам проверять токен и области действия аутентифицированного пользователя:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Наконец, в файле конфигурации приложения config/auth.php
вы должны установить для параметра driver
раздела api
значение passport
. Это укажет вашему приложению использовать Passport TokenGuard
при аутентификации входящих запросов API:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
Развертывание Passport
При первом развертывании Passport на серверах вашего приложения вам, вероятно, потребуется выполнить команду passport:keys
. Эта команда генерирует ключи шифрования, необходимые Passport для создания токенов доступа. Сгенерированные ключи обычно не хранятся в системе контроля версий:
php artisan passport:keys
При необходимости вы можете указать путь, откуда должны быть загружены ключи Passport. Для этого вы можете использовать метод Passport::loadKeysFrom
. Обычно этот метод следует вызывать из метода boot
класса App\Providers\AppServiceProvider
вашего приложения:
/**
* Запустите любые службы приложений.
*/
public function boot(): void
{
Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
}
Загрузка ключей из окружения
В качестве альтернативы вы можете опубликовать файл конфигурации Passport с помощью Artisan-команды vendor:publish
:
php artisan vendor:publish --tag=passport-config
После публикации файла конфигурации вы можете загрузить ключи шифрования вашего приложения, определив их как переменные среды:
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<private key here>
-----END RSA PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<public key here>
-----END PUBLIC KEY-----"
Обновление Passport
При обновлении до новой основной версии Passport важно внимательно изучить руководство по обновлению.
Настройка
Срок жизни токена
По умолчанию Passport выдает долговременные токены доступа, срок действия которых истекает через год. Если вы хотите настроить более длительный / более короткий срок жизни токена, вы можете использовать методы tokensExpireIn
, refreshTokensExpireIn
и personalAccessTokensExpireIn
. Эти методы следует вызывать из метода boot
класса App\Providers\AppServiceProvider
вашего приложения:
use Carbon\CarbonInterval;
/**
* Запустите любые службы приложений.
*/
public function boot(): void
{
Passport::tokensExpireIn(CarbonInterval::days(15));
Passport::refreshTokensExpireIn(CarbonInterval::days(30));
Passport::personalAccessTokensExpireIn(CarbonInterval::months(6));
}
Поля
expires_at
в таблицах базы данных Passport доступны только для чтения и только для отображения. При выпуске токенов Passport сохраняет информацию об истечении срока действия в подписанных и зашифрованных токенах. Если вам нужно сделать токен недействительным, вы должны отозвать его.
Переопределение моделей по умолчанию
Вы можете свободно расширять модели, используемые внутри Passport, определяя свою собственную модель и расширяя соответствующую модель Passport:
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}
После определения модели вы можете указать Passport использовать вашу пользовательскую модель через класс Laravel\Passport\Passport
. Как правило, вы должны сообщить Passport о ваших пользовательских моделях в методе boot
класса App\Providers\AppServiceProvider
вашего приложения:
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\DeviceCode;
use App\Models\Passport\RefreshToken;
use App\Models\Passport\Token;
use Laravel\Passport\Passport;
/**
* Запустите любые службы приложений.
*/
public function boot(): void
{
Passport::useTokenModel(Token::class);
Passport::useRefreshTokenModel(RefreshToken::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::useClientModel(Client::class);
Passport::useDeviceCodeModel(DeviceCode::class);
}
Переопределение маршрутов
Иногда может возникнуть необходимость настроить маршруты, определенные Passport. Для этого сначала нужно игнорировать маршруты, зарегистрированные Passport, добавив Passport::ignoreRoutes()
в метод register
класса AppServiceProvider
вашего приложения:
use Laravel\Passport\Passport;
/**
* Регистрация любых сервисов приложения.
*/
public function register(): void
{
Passport::ignoreRoutes();
}
Затем вы можете скопировать маршруты, определенные Passport, из его файла маршрутов в файл routes/web.php
вашего приложения и изменить их по своему усмотрению:
Route::group([
'as' => 'passport.',
'prefix' => config('passport.path', 'oauth'),
'namespace' => '\Laravel\Passport\Http\Controllers',
], function () {
// Маршруты Passport...
});
Этот подход позволяет вам полностью контролировать маршруты, связанные с Passport, и настраивать их в соответствии с потребностями вашего приложения.
Предоставление кода авторизации
Использование OAuth2 через коды авторизации — это то, через что большинство разработчиков знакомится с OAuth2. При использовании кодов авторизации клиентское приложение перенаправит пользователя на ваш сервер, где он либо утвердит, либо отклонит запрос на выдачу токена доступа клиенту.
Для начала нам нужно указать Passport, как возвращать наше «авторизованное» представление.
Вся логика рендеринга представления авторизации может быть настроена с помощью соответствующих методов, доступных в классе Laravel\Passport\Passport
. Как правило, этот метод следует вызывать из метода boot
класса App\Providers\AppServiceProvider
вашего приложения:
use Inertia\Inertia;
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// By providing a view name...
Passport::authorizationView('auth.oauth.authorize');
// By providing a closure...
Passport::authorizationView(
fn ($parameters) => Inertia::render('Auth/OAuth/Authorize', [
'request' => $parameters['request'],
'authToken' => $parameters['authToken'],
'client' => $parameters['client'],
'user' => $parameters['user'],
'scopes' => $parameters['scopes'],
])
);
}
Passport автоматически определит маршрут /oauth/authorize
, возвращающий это представление. Ваш шаблон auth.oauth.authorize
должен включать форму, которая отправляет POST-запрос к маршруту passport.authorizations.approve
для подтверждения авторизации, и форму, которая отправляет DELETE-запрос к маршруту passport.authorizations.deny
для отклонения авторизации. Маршруты passport.authorizations.approve
и passport.authorizations.deny
ожидают поля state
, client_id
и auth_token
.
Управление клиентами
Разработчикам приложений, которым необходимо взаимодействовать с API вашего приложения, необходимо зарегистрировать своё приложение в вашем приложении, создав «клиента». Обычно это включает в себя указание имени приложения и URI, на который ваше приложение будет перенаправлять пользователей после одобрения их запроса на авторизацию.
Собственные клиенты
Самый простой способ создать клиент — использовать команду Artisan passport:client
. Эта команда может использоваться для создания собственных клиентов или тестирования функциональности OAuth2. При запуске команды passport:client
Passport запросит у вас дополнительную информацию о клиенте и предоставит идентификатор и секретный ключ клиента:
php artisan passport:client
Если вы хотите разрешить несколько URI перенаправления для вашего клиента, вы можете указать их, разделив запятыми, в запросе URI командой passport:client
. Все URI, содержащие запятые, должны быть закодированы в URI:
https://third-party-app.com/callback,https://example.com/oauth/redirect
Сторонние клиенты
Поскольку пользователи вашего приложения не смогут использовать команду passport:client
, вы можете использовать метод createAuthorizationCodeGrantClient
класса Laravel\Passport\ClientRepository
для регистрации клиента для заданного пользователя:
use App\Models\User;
use Laravel\Passport\ClientRepository;
$user = User::find($userId);
// Создание клиента приложения OAuth, принадлежащего данному пользователю...
$client = app(ClientRepository::class)->createAuthorizationCodeGrantClient(
user: $user,
name: 'Example App',
redirectUris: ['https://third-party-app.com/callback'],
confidential: false,
enableDeviceFlow: true
);
// Получение всех клиентов приложения OAuth, принадлежащих пользователю...
$clients = $user->oauthApps()->get();
Метод createAuthorizationCodeGrantClient
возвращает экземпляр Laravel\Passport\Client
. Вы можете отобразить $client->id
в качестве идентификатора клиента, а $client->plainSecret
— в качестве секретного ключа клиента.
Запрос токенов
Перенаправление для авторизации
После создания клиента разработчики могут использовать свой идентификатор клиента и секретный ключ, чтобы запросить код авторизации и токен доступа из вашего приложения. Во-первых, приложение-потребитель должно сделать запрос перенаправления на маршрут вашего приложения /oauth/authorize
следующим образом:
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
'state' => $state,
// 'prompt' => '', // "none", "consent", or "login"
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
Параметр prompt
может использоваться для определения поведения аутентификации в приложении Passport.
Если значение prompt
равно none
, Passport всегда будет выдавать ошибку аутентификации, если пользователь не аутентифицирован в приложении Passport. Если значение равно consent
, Passport всегда будет отображать экран одобрения авторизации, даже если все разрешения были ранее предоставлены потребляющему приложению. Когда значение равно login
, приложение Passport всегда будет предлагать пользователю повторно войти в систему, даже если у него уже есть активная сессия.
Если значение prompt
не указано, пользователь будет приглашен к авторизации только в том случае, если он ранее не авторизовал доступ потребляющему приложению для запрашиваемых разрешений.
Помните, что маршрут
/oauth/authorize
уже определен в Passport. Вам не нужно вручную определять этот маршрут.
Подтверждение запроса
При получении запросов на авторизацию Passport автоматически реагирует в соответствии со значением параметра prompt
(если он присутствует) и может отображать пользователю шаблон, позволяющий одобрить или отклонить запрос на авторизацию. Если пользователь одобряет запрос, он будет перенаправлен обратно на redirect_uri
, который был указан потребляющим приложением. redirect_uri
должен соответствовать URL-адресу перенаправления, который был указан при создании клиента.
Иногда вам может понадобиться пропустить запрос на авторизацию, например, при авторизации основного клиента. Вы можете сделать это, расширив модель Client
и определив метод skipsAuthorization
. Если skipsAuthorization
возвращает true
, клиент будет автоматически одобрен, и пользователь будет немедленно перенаправлен обратно на redirect_uri
, за исключением случаев, когда потребляющее приложение явно установило параметр prompt
при перенаправлении на авторизацию:
<?php
namespace App\Models\Passport;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Passport\Client as BaseClient;
class Client extends BaseClient
{
/**
* Определите, должен ли клиент пропускать запрос авторизации.
*
* @param \Laravel\Passport\Scope[] $scopes
*/
public function skipsAuthorization(Authenticatable $user, array $scopes): bool
{
return $this->firstParty();
}
}
Преобразование кодов авторизации в токены доступа
Если пользователь одобряет запрос авторизации, он будет перенаправлен обратно в приложение-потребитель. Потребитель должен сначала сверить параметр state
со значением, которое было сохранено до перенаправления. Если параметр state
совпадает, то потребитель должен отправить вашему приложению запрос POST
, чтобы запросить токен доступа. Запрос должен включать код авторизации, который был выдан вашим приложением, когда пользователь утвердил запрос авторизации:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class,
'Invalid state value.'
);
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'redirect_uri' => 'https://third-party-app.com/callback',
'code' => $request->code,
]);
return $response->json();
});
Маршрут /oauth/token
вернет ответ JSON, содержащий атрибуты access_token
, refresh_token
и expires_in
. Атрибут expires_in
содержит количество секунд до истечения срока действия токена доступа.
Как и маршрут
/oauth/authorize
, маршрут/oauth/token
определяется для вас методомPassport::routes
. Нет необходимости определять этот маршрут вручную.
Управление токенами
Вы можете получить авторизованные токены пользователя, используя метод tokens
трейта Laravel\Passport\HasApiTokens
. Например, это может быть использовано, чтобы предоставить вашим пользователям панель управления для отслеживания их подключений к сторонним приложениям:
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;
$user = User::find($userId);
// Получение всех действительных токенов для пользователя...
$tokens = $user->tokens()
->where('revoked', false)
->where('expires_at', '>', Date::now())
->get();
// Получение всех подключений пользователя к сторонним клиентам приложений OAuth...
$connections = $tokens->load('client')
->reject(fn (Token $token) => $token->client->firstParty())
->groupBy('client_id')
->map(fn (Collection $tokens) => [
'client' => $tokens->first()->client,
'scopes' => $tokens->pluck('scopes')->flatten()->unique()->values()->all(),
'tokens_count' => $tokens->count(),
])
->values();
Обновление токенов
Если ваше приложение выдает недолговечные токены доступа, пользователям потребуется обновить свои токены доступа с помощью токена обновления, предоставленного им при выдаче токена доступа:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'scope' => 'user:read orders:create',
]);
return $response->json();
Этот маршрут /oauth/token
вернет ответ JSON, содержащий атрибуты access_token
, refresh_token
и expires_in
. Атрибут expires_in
содержит количество секунд до истечения срока действия токена доступа.
Отзыв токенов
Вы можете отозвать токен, используя метод revoke
модели Laravel\Passport\Token
. Вы можете отозвать токен обновления токена, используя метод revoke
модели Laravel\Passport\RefreshToken
:
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
$token = Passport::token()->find($tokenId);
// Отозвать токен доступа...
$token->revoke();
// Отозвать токен обновления токена...
$token->refreshToken?->revoke();
// Отозвать все токены пользователя...
User::find($userId)->tokens()->each(function (Token $token) {
$token->revoke();
$token->refreshToken?->revoke();
});
Удаление токенов
Когда токены были отозваны или срок их действия истек, вы можете удалить их из базы данных. Команда passport:purge
Artisan, содержащаяся в Passport, может сделать это за вас:
# Удалить отозванные и просроченные токены, коды авторизации и коды устройств...
php artisan passport:purge
# Удалить токены срок действия которых истек более чем на 6 часов назад...
php artisan passport:purge --hours=6
# Удалить только отозванные токены, коды авторизации и коды устройств...
php artisan passport:purge --revoked
# Удалить только просроченные токены, коды авторизации и коды устройств...
php artisan passport:purge --expired
Вы также можете настроить запланированное задание в файле вашего приложения routes/console.php
для автоматического удаления токенов по расписанию:
use Illuminate\Support\Facades\Schedule;
Schedule::command('passport:purge')->hourly();
Предоставление кода авторизации с помощью PKCE
Предоставление кода авторизации с Proof Key for Code Exchange
(PKCE) – это безопасный способ аутентификации одностраничных или мобильных приложений для доступа к вашему API. Это разрешение следует использовать, когда вы не можете гарантировать, что секретный ключ клиента будет храниться конфиденциально, или, чтобы уменьшить угрозу перехвата кода авторизации злоумышленником. Комбинация code verifier
и code challenge
заменяет секретный ключ клиента при замене кода авторизации на токен доступа.
Создание клиента
Прежде чем ваше приложение сможет выдавать токены через предоставление кода авторизации с помощью PKCE, вам необходимо создать клиента с поддержкой PKCE. Вы можете сделать это с помощью Artisan-команды passport:client
с параметром --public
:
php artisan passport:client --public
Запрос токенов
Code Verifier & Code Challenge
Поскольку это разрешение на авторизацию не предоставляет секретный ключ клиента, разработчикам необходимо сгенерировать комбинацию code verifier
и code challenge
, чтобы запросить токен.
Средство проверки кода должно представлять собой случайную строку от 43 до 128 символов, содержащую буквы, цифры и символы "-"
, "."
, "_"
, "~"
, как определено в спецификации RFC 7636.
Итоговым результатом должна быть строка в кодировке Base64 с URL-адресом и безопасными для имени файла символами. Завершающие символы '=' должны быть удалены, и не должно быть разрывов строк, пробелов или других дополнительных символов.
$encoded = base64_encode(hash('sha256', $code_verifier, true));
$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');
Перенаправление для авторизации
После создания клиента вы можете использовать идентификатор клиента и сгенерированный code verifier
и code challenge
, чтобы запросить код авторизации и токен доступа из вашего приложения. Во-первых, приложение-потребитель должно сделать запрос перенаправления на маршрут вашего приложения /oauth/authorize
:
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$request->session()->put(
'code_verifier', $code_verifier = Str::random(128)
);
$codeChallenge = strtr(rtrim(
base64_encode(hash('sha256', $code_verifier, true))
, '='), '+/', '-_');
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
// 'prompt' => '', // "none", "consent", or "login"
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
Преобразование кодов авторизации в токены доступа
Если пользователь одобряет запрос авторизации, он будет перенаправлен обратно в приложение-потребитель. Потребитель должен сверить параметр state
со значением, которое было сохранено до перенаправления, как в стандартном предоставлении кода авторизации.
Если параметр состояния совпадает, потребитель должен отправить вашему приложению запрос POST
, чтобы запросить токен доступа. Запрос должен включать код авторизации, который был выдан вашим приложением, когда пользователь утвердил запрос авторизации, вместе с первоначально сгенерированным верификатором кода:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
$codeVerifier = $request->session()->pull('code_verifier');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class
);
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'code_verifier' => $codeVerifier,
'code' => $request->code,
]);
return $response->json();
});
Предоставление авторизации устройству
Разрешение на авторизацию устройства OAuth2 позволяет устройствам ввода без браузера или с ограниченными возможностями, таким как телевизоры и игровые консоли, получать токен доступа путем обмена «кодом устройства». При использовании Device Flow клиент устройства предложит пользователю использовать дополнительное устройство, например компьютер или смартфон, и подключиться к вашему серверу, где он введет предоставленный «код пользователя» и либо одобрит, либо отклонит запрос на доступ.
Для начала нам нужно указать Passport, как возвращать наши представления «код пользователя» и «авторизация».
Вся логика рендеринга представления авторизации может быть настроена с помощью соответствующих методов, доступных в классе Laravel\Passport\Passport
. Как правило, этот метод следует вызывать из метода boot
класса App\Providers\AppServiceProvider
вашего приложения.
use Inertia\Inertia;
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Указав имя представления...
Passport::deviceUserCodeView('auth.oauth.device.user-code');
Passport::deviceAuthorizationView('auth.oauth.device.authorize');
// Указав замыкание...
Passport::deviceUserCodeView(
fn ($parameters) => Inertia::render('Auth/OAuth/Device/UserCode')
);
Passport::deviceAuthorizationView(
fn ($parameters) => Inertia::render('Auth/OAuth/Device/Authorize', [
'request' => $parameters['request'],
'authToken' => $parameters['authToken'],
'client' => $parameters['client'],
'user' => $parameters['user'],
'scopes' => $parameters['scopes'],
])
);
// ...
}
Passport автоматически определит маршруты, возвращающие эти представления. Ваш шаблон auth.oauth.device.user-code
должен включать форму, которая отправляет GET-запрос к маршруту passport.device.authorizations.authorize
. Маршрут passport.device.authorizations.authorize
ожидает параметр запроса user_code
.
Ваш шаблон auth.oauth.device.authorize
должен включать форму, которая отправляет POST-запрос к маршруту passport.device.authorizations.approve
для подтверждения авторизации, и форму, которая отправляет DELETE-запрос к маршруту passport.device.authorizations.deny
для отклонения авторизации. Маршруты passport.device.authorizations.approve
и passport.device.authorizations.deny
ожидают поля state
, client_id
и auth_token
.
Создание клиента предоставления авторизации устройства
Прежде чем ваше приложение сможет выдавать токены через предоставление авторизации устройству, вам необходимо создать клиент с поддержкой Device Flow. Это можно сделать с помощью команды Artisan passport:client
с опцией --device
. Эта команда создаст клиент с поддержкой Device Flow и предоставит вам идентификатор и секретный ключ клиента:
php artisan passport:client --device
Кроме того, вы можете использовать метод createDeviceAuthorizationGrantClient
класса ClientRepository
для регистрации стороннего клиента, принадлежащего данному пользователю:
use App\Models\User;
use Laravel\Passport\ClientRepository;
$user = User::find($userId);
$client = app(ClientRepository::class)->createDeviceAuthorizationGrantClient(
user: $user,
name: 'Example Device',
confidential: false,
);
Запрос токенов
Запрос кода устройства
После создания клиента разработчики могут использовать идентификатор клиента для запроса кода устройства из вашего приложения. Сначала устройство-потребитель должно отправить POST-запрос к маршруту /oauth/device/code
вашего приложения, чтобы запросить код устройства:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/device/code', [
'client_id' => 'your-client-id',
'scope' => 'user:read orders:create',
]);
return $response->json();
В результате будет возвращён JSON-ответ, содержащий атрибуты device_code
, user_code
, verification_uri
, interval
и expires_in
. Атрибут expires_in
содержит количество секунд до истечения срока действия кода устройства. Атрибут interval
содержит количество секунд, которое устройство-потребитель должно ждать между запросами при опросе маршрута /oauth/token
, чтобы избежать ошибок ограничения скорости.
Помните, что маршрут
/oauth/device/code
уже определён в Passport. Вам не нужно определять этот маршрут вручную.
Отображение URI проверки и кода пользователя
После получения запроса на код устройства потребляющее устройство должно проинструктировать пользователя использовать другое устройство, посетить предоставленный verification_uri
и ввести user_code
, чтобы одобрить запрос на авторизацию.
Запрос опросного токена
Поскольку пользователь будет использовать отдельное устройство для предоставления (или запрета) доступа, устройство-потребитель должно опрашивать маршрут /oauth/token
вашего приложения, чтобы определить, когда пользователь ответил на запрос. Устройство-потребитель должно использовать минимальный интервал опроса, указанный в JSON-ответе, при запросе кода устройства, чтобы избежать ошибок, связанных с ограничением частоты запросов:
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Sleep;
$interval = 5;
do {
Sleep::for($interval)->seconds();
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Required for confidential clients only...
'device_code' => 'the-device-code',
]);
if ($response->json('error') === 'slow_down') {
$interval += 5;
}
} while (in_array($response->json('error'), ['authorization_pending', 'slow_down']));
return $response->json();
Если пользователь одобрил запрос авторизации, будет возвращён JSON-ответ, содержащий атрибуты access_token
, refresh_token
и expires_in
. Атрибут expires_in
содержит количество секунд до истечения срока действия токена доступа.
Предоставление пароля
Мы больше не рекомендуем использовать токены для предоставления пароля. Вместо этого вам следует выбрать тип гаранта, который в настоящее время рекомендуется OAuth2 Server.
Предоставление пароля OAuth2 позволяет другим сторонним клиентам, таким как мобильное приложение, получать токен доступа, используя адрес электронной почты / имя пользователя и пароль. Это позволяет вам безопасно выдавать токены доступа своим основным клиентам, не требуя от пользователей прохождения всего потока перенаправления кода авторизации OAuth2.
Чтобы включить предоставление пароля, вызовите метод enablePasswordGrant
в методе boot
класса App\Providers\AppServiceProvider
вашего приложения:
/**
* Запустите любые службы приложения.
*/
public function boot(): void
{
Passport::enablePasswordGrant();
}
Создание токенов
Прежде чем ваше приложение сможет выдавать токены с помощью предоставления пароля, вам необходимо создать клиент предоставления пароля. Вы можете сделать это с помощью Artisan-команды passport:client
с параметром --password
.
php artisan passport:client --password
Запрос токенов
После включения предоставления пароля и создания клиента с предоставлением пароля вы можете запросить токен доступа, отправив POST
запрос на маршрут /oauth/token
с адресом электронной почты и паролем пользователя. Помните, что этот маршрут уже зарегистрирован Passport, поэтому нет необходимости определять его вручную. Если запрос будет успешным, вы получите access_token
и refresh_token
в JSON-ответе от сервера:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Требуется только для конфиденциальных клиентов...
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => 'user:read orders:create',
]);
return $response->json();
Помните, токены доступа по умолчанию являются долгоживущими. Однако вы можете настроить максимальное время жизни токена доступа, если это необходимо.
Запрос токена для всех областей
При использовании доступа по паролю или доступа с учетными данными клиента вы можете авторизовать токен для всех областей, поддерживаемых вашим приложением. Вы можете сделать это, указав *
в параметре scope
. При этом метод can
экземпляра токена всегда будет возвращать true
. Эта расширенная область может быть назначена только токену, который выпущен с использованием разрешений password
или client_credentials
:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Требуется только для конфиденциальных клиентов...
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => '*',
]);
Настройка пользовательского провайдера
Если ваше приложение использует более одного провайдера аутентификации пользователя, вы можете указать, какой провайдер использует клиент предоставления пароля, указав параметр --provider
при создании клиента через команду artisan passport:client --password
. Указанное имя провайдера должно соответствовать допустимому провайдеру, определенному в файле конфигурации приложения config/auth.php
. Затем вы можете защитить свой маршрут с помощью посредника, чтобы гарантировать, что авторизованы только пользователи из указанного провайдера.
Настройка поля имени пользователя
При аутентификации с использованием предоставления пароля Passport будет использовать атрибут email
вашей аутентифицируемой модели в качестве “username”. Однако вы можете настроить это поведение, определив метод findForPassport
в своей модели:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable;
/**
* Возвращает экземпляр пользователя для переданного имени.
*/
public function findForPassport(string $username): User
{
return $this->where('username', $username)->first();
}
}
Настройка проверки пароля пользователя
При аутентификации с использованием предоставления пароля Passport будет использовать атрибут password
модели для проверки пароля. Если модель не имеет атрибута password
или вы хотите настроить логику проверки пароля, вы можете определить метод validateForPassportPasswordGrant
в своей модели:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable;
/**
* Проверьте пароль пользователя для предоставления разрешения.
*/
public function validateForPassportPasswordGrant(string $password): bool
{
return Hash::check($password, $this->password);
}
}
Неявное разрешение
Мы больше не рекомендуем использовать токены для предоставления пароля. Вместо этого вам следует выбрать тип гаранта, который в настоящее время рекомендуется OAuth2 Server.
Неявное разрешение аналогично предоставлению кода авторизации; однако токен возвращается клиенту без обмена кодом авторизации. Это разрешение чаще всего используется для JavaScript или мобильных приложений, где учетные данные клиента не могут быть надежно сохранены. Чтобы включить разрешение, вызовите метод enableImplicitGrant
в методе boot
класса App\Providers\AppServiceProvider
вашего приложения:
/**
* Запустите любые службы приложения.
*/
public function boot(): void
{
Passport::enableImplicitGrant();
}
Прежде чем ваше приложение сможет выдавать токены через неявное предоставление, вам необходимо создать клиент неявного предоставления. Это можно сделать с помощью Artisan-команды passport:client
с опцией --implicit
.
php artisan passport:client --implicit
После включения разрешения и создания неявного клиента разработчики могут использовать свой идентификатор клиента для запроса токена доступа из вашего приложения. Приложение-потребитель должно выполнить перенаправление на маршрут /oauth/authorize
вашего приложения следующим образом:
use Illuminate\Http\Request;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'token',
'scope' => 'user:read orders:create',
'state' => $state,
// 'prompt' => '', // "none", "consent", or "login"
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
Помните, что маршрут
/oauth/authorize
уже определен методомPassport::routes
. Вам не нужно вручную определять этот маршрут.
Разрешение учетных данных
Предоставление учетных данных клиента подходит для межмашинной (machine-to-machine) аутентификации. Например, вы можете использовать это разрешение в запланированном задании, которое выполняет задачи обслуживания через API.
Прежде чем ваше приложение сможет выдавать токены с помощью предоставления учетных данных клиента, вам необходимо создать клиента предоставления учетных данных. Вы можете сделать это, используя параметр --client
в Artisan-команде passport:client
:
php artisan passport:client --client
Затем назначьте посредника Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner
маршруту:
use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner;
Route::get('/orders', function (Request $request) {
// Токен доступа действителен, и клиент является владельцем ресурса...
})->middleware(EnsureClientIsResourceOwner::class);
Чтобы ограничить доступ к маршруту определенными областями, вы можете предоставить список требуемых областей методу using
:
Route::get('/orders', function (Request $request) {
// Токен доступа действителен, клиент является владельцем ресурса и имеет обе области действия: «servers:read» и «servers:create»...
})->middleware(EnsureClientIsResourceOwner::using('servers:read', 'servers:create'));
Получение токенов
Чтобы получить токен с использованием этого типа разрешения, сделайте запрос к конечной точке oauth/token
:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'scope' => 'servers:read servers:create',
]);
return $response->json()['access_token'];
Токены персонального доступа
Иногда ваши пользователи могут захотеть выдать себе токены доступа, не проходя типичный поток перенаправления кода авторизации. Разрешение пользователям выдавать себе токены через пользовательский интерфейс вашего приложения может быть полезно для предоставления пользователям возможности экспериментировать с вашим API или может служить более простым подходом к выдаче токенов доступа в целом.
Если ваше приложение использует Passport в основном для выдачи токенов личного доступа, рассмотрите возможность использования Laravel Sanctum, облегченной собственной библиотеки Laravel для выдачи токенов доступа к API.
Создание токенов персонального доступа
Прежде чем ваше приложение сможет выдавать токены персонального доступа, вам необходимо создать клиента личного доступа. Вы можете сделать это, выполнив Artisan-команду passport:client
с параметром --personal
. Если вы уже выполнили команду passport:install
, вам не нужно запускать эту команду:
php artisan passport:client --personal
Настройка поставщика услуг для пользователей
Если ваше приложение использует более одного поставщика аутентификации пользователей, вы можете указать, какой поставщик используется клиентом предоставления личного доступа, указав параметр --provider
при создании клиента командой artisan passport:client --personal
. Указанное имя поставщика должно соответствовать допустимому имени поставщика, указанному в файле конфигурации config/auth.php
вашего приложения. После этого вы можете защитить свой маршрут с помощью посредника, чтобы гарантировать авторизацию только пользователей указанного провайдера.
Управление токенами персонального доступа
После того как вы создали клиент персонального доступа, вы можете выдавать токены для данного пользователя, используя метод createToken
в экземпляре модели App\Models\User
. Метод createToken
принимает имя токена в качестве первого аргумента и необязательный массив области в качестве второго аргумента:
use App\Models\User;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;
$user = User::find($userId);
// Создание токена без областей действия...
$token = $user->createToken('My Token')->accessToken;
// Создание токена с областями действия...
$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken;
// Создание токена со всеми областями действия...
$token = $user->createToken('My Token', ['*'])->accessToken;
// Извлечение всех действительных персональных токенов доступа, принадлежащих пользователю...
$tokens = $user->tokens()
->with('client')
->where('revoked', false)
->where('expires_at', '>', Date::now())
->get()
->filter(fn (Token $token) => $token->client->hasGrantType('personal_access'));
Защита маршрутов
Защита маршрутов через посредников
Паспорт включает в себя защиту аутентификации, которая проверяет токены доступа при входящих запросах. После того как вы настроили защиту api
для использования драйвера passport
, вам нужно указать посредника auth:api
на всех маршрутах, для которых требуется действующий токен доступа:
Route::get('/user', function () {
// Доступ к этому маршруту могут получить только пользователи, прошедшие аутентификацию API...
})->middleware('auth:api');
Если вы используете токены учетных данных, вы должны вместо этого использовать посредник
Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner
для защиты ваших маршрутовauth:api
.
Множественная защита аутентификации
Если ваше приложение аутентифицирует разные типы пользователей, которые, возможно, используют совершенно разные модели Eloquent, вам, вероятно, потребуется определить конфигурацию защиты для каждого типа провайдера пользователей в вашем приложении. Это позволяет защитить запросы, предназначенные для конкретных поставщиков услуг. Например, при следующей конфигурации защиты конфигурационный файл config/auth.php
:
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'api-customers' => [
'driver' => 'passport',
'provider' => 'customers',
],
],
Следующий маршрут будет использовать защиту api-customers
, которая использует провайдера пользователей customers
для аутентификации входящих запросов:
Route::get('/customer', function () {
// ...
})->middleware('auth:api-customers');
Для получения дополнительной информации об использовании нескольких поставщиков пользователей с Passport обратитесь к документации по персональным токенам доступа и документации по предоставлению пароля.
Защита маршрутов через передачу токена
При вызове маршрутов, защищенных Passport, пользователи API вашего приложения должны указать свой токен доступа как токен Bearer в заголовке Authorization своего запроса. Например, при использовании фасада Http
:
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Accept' => 'application/json',
'Authorization' => "Bearer $accessToken",
])->get('https://passport-app.test/api/user');
return $response->json();
Области токенов
Области позволяют вашим клиентам API запрашивать определенный набор разрешений при запросе авторизации для доступа к учетной записи. Например, если вы создаете приложение для электронной коммерции, не всем потребителям API потребуется возможность размещать заказы. Вместо этого вы можете разрешить потребителям запрашивать авторизацию только для доступа к статусам отгрузки заказа. Другими словами, области позволяют пользователям вашего приложения ограничивать действия, которые стороннее приложение может выполнять от их имени.
Определение областей
Вы можете определить области своего API, используя метод Passport::tokensCan
в методе boot
класса App\Providers\AppServiceProvider
вашего приложения. Метод tokensCan
принимает массив имен и описаний областей видимости. Описание области действия может быть любым, и оно будет отображаться для пользователей на экране утверждения авторизации:
/**
* Запустите любые службы приложения.
*/
public function boot(): void
{
Passport::tokensCan([
'user:read' => 'Retrieve the user info',
'orders:create' => 'Place orders',
'orders:read:status' => 'Check order status',
]);
}
Области по-умолчанию
Если клиент не запрашивает какие-либо конкретные области действия, вы можете настроить сервер Passport для присоединения областей действия по умолчанию к токену с помощью метода defaultScopes
. Как правило, этот метод следует вызывать из метода boot
класса App\Providers\AppServiceProvider
вашего приложения:
use Laravel\Passport\Passport;
Passport::tokensCan([
'user:read' => 'Retrieve the user info',
'orders:create' => 'Place orders',
'orders:read:status' => 'Check order status',
]);
Passport::defaultScopes([
'user:read',
'orders:create',
]);
Назначение областей токенам
При запросе кодов авторизации
При запросе токена доступа с использованием предоставления кода авторизации потребители должны указать свои желаемые области в качестве параметра строки запроса scope
. Параметр scope
должен быть списком областей, разделенных пробелами:
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
При выдаче токенов личного доступа
Если вы выдаете токены личного доступа с помощью метода createToken
модели App\Models\User
, вы можете передать массив желаемых областей в качестве второго аргумента метода:
$token = $user->createToken('My Token', ['place-orders'])->accessToken;
Проверка областей
В состав Passport входят два посредника, которые можно использовать для проверки подлинности входящего запроса с помощью токена, которому предоставлена определенная область действия.
Проверка всех областей
Посредник Laravel\Passport\Http\Middleware\CheckToken
может быть назначен маршруту для проверки того, что токен доступа входящего запроса имеет все перечисленные области действия:
use Laravel\Passport\Http\Middleware\CheckToken;
Route::get('/orders', function () {
// Access token has both "orders:read" and "orders:create" scopes...
})->middleware(['auth:api', CheckToken::using('orders:read', 'orders:create')]);
Проверка любых областей
Посредник Laravel\Passport\Http\Middleware\CheckTokenForAnyScope
может быть назначен маршруту для проверки того, что токен доступа входящего запроса имеет по крайней мере одну из перечисленных областей:
use Laravel\Passport\Http\Middleware\CheckTokenForAnyScope;
Route::get('/orders', function () {
// Access token has either "orders:read" or "orders:create" scope...
})->middleware(['auth:api', CheckTokenForAnyScope::using('orders:read', 'orders:create')]);
Проверка областей на экземпляре токена
После того как запрос с аутентификацией токена доступа поступил в ваше приложение, вы все равно можете проверить, имеет ли токен заданную область действия, используя метод tokenCan
в экземпляре App\Models\User
:
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
if ($request->user()->tokenCan('orders:create')) {
// ...
}
});
Дополнительные методы области
Метод scopeIds
вернет массив всех определенных идентификаторов / имен:
use Laravel\Passport\Passport;
Passport::scopeIds();
Метод scopes
вернет массив всех определенных областей как экземпляры Laravel\Passport\Scope
:
Passport::scopes();
Метод scopesFor
вернет массив экземпляров Laravel\Passport\Scope
, соответствующих указанным идентификаторам / именам:
Passport::scopesFor(['user:read', 'orders:create']);
Вы можете определить, была ли определена область, используя метод hasScope
:
Passport::hasScope('orders:create');
Аутентификация SPA
При создании API может быть чрезвычайно полезно иметь возможность использовать собственный API из приложения JavaScript. Такой подход к разработке API позволяет вашему собственному приложению использовать тот же API, которым вы делитесь со всем миром. Один и тот же API может использоваться вашим веб-приложением, мобильными приложениями, сторонними приложениями и любыми SDK, которые вы можете публиковать в различных менеджерах пакетов.
Как правило, если вы хотите использовать свой API из своего приложения JavaScript, вам необходимо вручную отправить токен доступа в приложение и передавать его с каждым запросом к вашему приложению. Однако Passport включает в себя посредника, которое может сделать это за вас. Все, что вам нужно сделать, это добавить посредника CreateFreshApiToken
в группу посредников web
в файле bootstrap/app.php
вашего приложения:
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
CreateFreshApiToken::class,
]);
})
Вы должны убедиться, что посредник
CreateFreshApiToken
является последним в списке ваших посредников указанных ранее.
Это посредник будет прикреплять файл cookie laravel_token
к вашим исходящим ответам. Этот файл cookie содержит зашифрованный JWT, который Passport будет использовать для аутентификации запросов API от вашего приложения JavaScript. Время жизни JWT равно вашему значению конфигурации session.lifetime. Теперь, поскольку браузер автоматически отправляет cookie со всеми последующими запросами, вы можете делать запросы к API вашего приложения без явной передачи токена доступа:
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
Настройка имени Cookie
При необходимости вы можете настроить имя файла cookie laravel_token
, используя метод Passport::cookie
. Обычно этот метод следует вызывать из метода boot
класса App\Providers\AppServiceProvider
вашего приложения:
/**
* Запустите любые службы приложения.
*/
public function boot(): void
{
Passport::cookie('custom_name');
}
CSRF защита
При использовании этого метода аутентификации вам необходимо убедиться, что в ваши запросы включен действительный заголовок токена CSRF. Стандартный шаблон Laravel JavaScript, входящий в состав скелетного приложения и всех стартовых наборов, включает экземпляр Axios, который будет автоматически использовать зашифрованное значение cookie XSRF-TOKEN
для отправки заголовка X-XSRF-TOKEN
в запросах.
Если вы решите отправить заголовок
X-CSRF-TOKEN
вместоX-XSRF-TOKEN
, вам нужно использовать незашифрованный токен, предоставленныйcsrf_token()
.
События
Passport вызывает события при выдаче токенов доступа и обновлении токенов. Вы можете прослушивать эти события, чтобы сократить или отозвать другие токены доступа в вашей базе данных:
Наименование события |
---|
Laravel\Passport\Events\AccessTokenCreated |
Laravel\Passport\Events\AccessTokenRevoked |
Laravel\Passport\Events\RefreshTokenCreated |
Тестирование
Метод actingAs
Passport может использоваться для указания аутентифицированного в данный момент пользователя, а также его областей действия. Первым аргументом, передаваемым методу actingAs
, является экземпляр пользователя, а вторым – массив областей видимости, которые должны быть предоставлены токену пользователя:
use App\Models\User;
use Laravel\Passport\Passport;
test('orders can be created', function () {
Passport::actingAs(
User::factory()->create(),
['orders:create']
);
$response = $this->post('/api/orders');
$response->assertStatus(201);
});
use App\Models\User;
use Laravel\Passport\Passport;
public function test_orders_can_be_created(): void
{
Passport::actingAs(
User::factory()->create(),
['orders:create']
);
$response = $this->post('/api/orders');
$response->assertStatus(201);
}
Метод actingAsClient
Passport может использоваться для указания аутентифицированного в данный момент клиента, а также его областей. Первым аргументом, передаваемым методу actingAsClient
, является экземпляр клиента, а вторым — массив областей видимости, которые должны быть предоставлены токену клиента:
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
test('servers can be retrieved', function () {
Passport::actingAsClient(
Client::factory()->create(),
['servers:read']
);
$response = $this->get('/api/servers');
$response->assertStatus(200);
});
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
public function test_servers_can_be_retrieved(): void
{
Passport::actingAsClient(
Client::factory()->create(),
['servers:read']
);
$response = $this->get('/api/servers');
$response->assertStatus(200);
}