Любите загадки? Событие еще доступно на сайте.
Поделитесь своим кодом и идеями!
Поделитесь своим кодом и идеями!

Пакет Laravel Sanctum

Введение

Laravel Sanctum предлагает легковесную систему аутентификации для SPA (одностраничных приложений), мобильных приложений и простых API на основе токенов. Sanctum позволяет каждому пользователю вашего приложения создавать несколько токенов API для своей учетной записи. Этим токенам могут быть предоставлены полномочия / области, которые определяют, какие действия токенам разрешено выполнять.

Как это работает

Laravel Sanctum существует для решения двух отдельных задач. Давайте обсудим каждую из них, прежде чем углубиться в изучение пакета.

Краткая информация о токенах API

Во-первых, Sanctum – это простой пакет, который вы можете использовать для выдачи токенов API своим пользователям без осложнений с OAuth. Этот функционал вдохновлен GitHub и другими приложениями, которые выдают «токены личного доступа». Например, представьте, что в «настройках учетной записи» вашего приложения есть экран, на котором пользователь может сгенерировать токен API для своей учетной записи. Вы можете использовать Sanctum для создания этих токенов и управления ими. Эти токены обычно имеют очень длительный срок действия (годы), но могут быть отозваны пользователем самостоятельно в любое время.

Laravel Sanctum предлагает этот функционал через сохранение токенов API пользователя в единой таблице базы данных и аутентифицируя входящие HTTP-запросы через заголовок Authorization, который должен содержать действительный токен API.

Краткая информация об аутентификации SPA

Во-вторых, Sanctum также обеспечивает простой метод аутентификации одностраничных приложений (SPA), взаимодействующих с API Laravel. Эти SPA могут существовать в том же репозитории, что и ваше приложение Laravel, или могут быть полностью отдельным репозиторием, например SPA, созданные с помощью Next.js или Nuxt.

Для этого функционала Sanctum не использует никаких токенов. Вместо этого Sanctum использует встроенные в Laravel службы аутентификации сессии на основе файлов cookie. Обычно для этого Sanctum использует охранника web аутентификации Laravel. Это обеспечивает преимущества защиты от CSRF, аутентификации сессии, а также защищает от утечки учетных данных аутентификации через XSS.

Sanctum будет пытаться аутентифицироваться с помощью файлов cookie только тогда, когда входящий запрос исходит от вашей собственной клиентской части SPA. Когда Sanctum проверяет входящий HTTP-запрос, он сначала проверяет файл cookie аутентификации, а если он отсутствует, то Sanctum проверяет заголовок Authorization на наличие действительного токена API.

Совершенно нормально использовать Sanctum только для аутентификации токена API или только для аутентификации SPA. Тот факт, что вы используете Sanctum, не означает, что вы должны использовать оба функционала, которые он предлагает.

Установка

Вы можете установить Laravel Sanctum с помощью Artisan-команды install:api:

php artisan install:api

Далее, если вы планируете использовать Sanctum для аутентификации SPA, обратитесь к разделу Аутентификация SPA этой документации.

Конфигурирование

Переопределение моделей по умолчанию

Хотя обычно это не требуется, но вы можете расширить модель PersonalAccessToken, используемую внутри Sanctum:

use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;

class PersonalAccessToken extends SanctumPersonalAccessToken
{
    // ...
}

Затем, вы можете указать Sanctum использовать вашу пользовательскую модель с помощью метода usePersonalAccessTokenModel, предоставленного Sanctum. Обычно этот метод следует вызывать в методе boot файла AppServiceProvider вашего приложения:

use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;

/**
 * Загрузка любых служб приложения.
 */
public function boot(): void
{
    Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}

Аутентификация токена API

Вы не должны использовать токены API для аутентификации собственного SPA. Вместо этого используйте функционал аутентификации SPA Sanctum.

Выдача токенов API

Sanctum позволяет выдавать токены API / персональные токены доступа, которые могут использоваться для аутентификации API-запросов к вашему приложению. При выполнении запросов с использованием токенов API, токен должен быть включен в заголовок Authorization как Bearer-токен.

Чтобы начать выдачу токенов для пользователей, ваша модель User должна использовать трейт Laravel\Sanctum\HasApiTokens:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}

Для выдачи токена вы можете использовать метод createToken. Метод createToken возвращает экземпляр Laravel\Sanctum\NewAccessToken. Токены API хешируются с использованием хеширования SHA-256 перед сохранением в вашей базе данных, но вы можете получить доступ к текстовому значению токена, используя свойство plainTextToken экземпляра NewAccessToken. Вы должны отобразить это значение пользователю сразу после создания токена:

use Illuminate\Http\Request;

Route::post('/tokens/create', function (Request $request) {
    $token = $request->user()->createToken($request->token_name);

    return ['token' => $token->plainTextToken];
});

Вы можете получить доступ ко всем токенам пользователя с помощью отношения Eloquent tokens трейта HasApiTokens:

foreach ($user->tokens as $token) {
    // ...
}

Полномочия токена

Sanctum позволяет вам назначать «полномочия» (abilities) токенам. Полномочия служат той же цели, что и «права доступа» OAuth Scopes. Вы можете передать массив, содержащий строковые ключи полномочий, в качестве второго аргумента методу createToken:

return $user->createToken('token-name', ['server:update'])->plainTextToken;

При обработке входящего запроса, аутентифицированного Sanctum, вы можете определить, обладает ли токен указанными полномочиями, используя метод tokenCan:

if ($user->tokenCan('server:update')) {
    // ...
}

Посредник для токена

Sanctum также включает в себя два посредника, которые можно использовать для проверки подлинности входящего запроса с помощью токена, которому предоставлена ​​данная возможность. Для начала определите следующие псевдонимы посредника в файле bootstrap/app.php вашего приложения:

use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'abilities' => CheckAbilities::class,
        'ability' => CheckForAnyAbility::class,
    ]);
})

Посредник abilities может быть назначен маршруту для проверки того, что токен входящего запроса имеет все перечисленные возможности:

Route::get('/orders', function () {
    // У токена есть возможности как для проверки статуса, так и для размещения заказов...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);

Посредник ability может быть назначен маршруту для проверки того, что токен входящего запроса имеет “по крайней мере одну” из перечисленных возможностей:

Route::get('/orders', function () {
    // Токен имеет возможность "проверить статус" или "разместить заказы"...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);

Однодоменные запросы, инициированные пользовательским интерфейсом

Для удобства метод tokenCan всегда будет возвращать true, если входящий аутентифицированный запрос был от вашего собственного SPA, и вы используете встроенную аутентификацию SPA Sanctum.

Однако, это не обязательно означает, что ваше приложение должно позволять пользователю выполнять действие. Как правило, политики авторизации вашего приложения определяют, предоставлены ли токену полномочия, а также проверяют, разрешено ли самому экземпляру пользователя выполнять действие.

Например, если мы представим себе приложение, которое управляет серверами, это может означать проверку того, что токен авторизован для обновления серверов и что сервер принадлежит пользователю:

return $request->user()->id === $server->user_id &&
       $request->user()->tokenCan('server:update')

Сначала может показаться странным допущение вызова метода tokenCan, возвращающего всегда true для запросов, инициированных пользовательским интерфейсом; однако, удобно иметь возможность всегда предполагать, что токен API доступен и может быть проверен с помощью метода tokenCan. Применяя этот подход, вы всегда можете вызвать метод tokenCan в политиках авторизации вашего приложения, не беспокоясь о том, был ли запрос инициирован из пользовательского интерфейса вашего приложения или был инициирован одним из сторонних потребителей вашего API.

Защита маршрутов

Чтобы защитить маршруты через обязательную аутентификацию входящих запросов, вы должны назначить охранника (guard) аутентификации sanctum вашим защищаемым маршрутам в файлах маршрутов routes/web.php и routes/api.php. Этот охранник гарантирует, что входящие запросы аутентифицируются либо как запросы с cookie, если запрос поступает от веб-страницы, сформированной Laravel, либо как запросы с токеном API, если запрос поступает от внешнего SPA- или мобильного приложения.

Вам может быть интересно, почему мы предлагаем вам аутентифицировать маршруты в файле routes/web.php вашего приложения, используя охранник sanctum. Помните, что Sanctum сначала попытается аутентифицировать входящие запросы, используя типичный файл cookie аутентификации сессии Laravel. Если этот файл cookie отсутствует, то Sanctum попытается аутентифицировать запрос, используя токен в заголовке Authorization запроса. Кроме того, аутентификация всех запросов с помощью Sanctum гарантирует, что мы всегда можем вызвать метод tokenCan для экземпляра текущего аутентифицированного пользователя:

use Illuminate\Http\Request;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

Отзыв токенов

Вы можете «отозвать» токены, удалив их из своей базы данных, используя отношение tokens трейта Laravel\Sanctum\HasApiTokens:

// Отзыв всех токенов ...
$user->tokens()->delete();

// Отозвать токен, который использовался для аутентификации текущего запроса ...
$request->user()->currentAccessToken()->delete();

// Отзыв определенного токена ...
$user->tokens()->where('id', $tokenId)->delete();

Срок действия токена

По умолчанию токены Sanctum никогда не истекают и могут быть аннулированы только путем отзыва токена. Однако, если вы хотите настроить время истечения для токенов API вашего приложения, вы можете сделать это через опцию expiration, определенную в файле конфигурации sanctum вашего приложения. Эта опция конфигурации определяет количество минут, после которых выданный токен будет считаться истекшим:

'expiration' => 525600,

Если вы хотите указать время истечения каждого токена отдельно, вы можете сделать это, передав время истечения в качестве третьего аргумента методу createToken:

return $user->createToken(
    'token-name', ['*'], now()->addWeek()
)->plainTextToken;

Если вы настроили время истечения токена для вашего приложения, вы также можете запланировать задачу для очистки истекших токенов вашего приложения. К счастью, Sanctum включает в себя команду Artisan sanctum:prune-expired, которую вы можете использовать для выполнения этой задачи. Например, вы можете настроить запланированную задачу для удаления всех записей об истекших токенах в базе данных, которые были истекшие как минимум 24 часа:

use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();

Аутентификация SPA

Sanctum также обеспечивает простой метод аутентификации одностраничных приложений (SPA), взаимодействующих с API Laravel. Эти SPA могут существовать в том же репозитории, что и ваше приложение Laravel, или могут быть полностью отдельным репозиторием.

Для этого функционала Sanctum не использует никаких токенов. Вместо этого Sanctum использует встроенные в Laravel службы аутентификации сессии на основе файлов Cookies. Такой подход к аутентификации обеспечивает преимущества защиты от CSRF, аутентификации сессии, а также защищает от утечки учетных данных аутентификации через XSS.

Для аутентификации вашего одностраничного приложения (SPA) и API, они должны иметь один и тот же домен верхнего уровня. Однако они могут находиться на разных поддоменах. Кроме того, убедитесь, что вы отправляете заголовок Accept: application/json и либо заголовок Referer, либо заголовок Origin в вашем запросе.

Конфигурирование SPA

Настройка собственных доменов

Во-первых, вы должны настроить, из каких доменов ваш SPA будет делать запросы. Вы можете указать эти домены, используя параметр stateful в конфигурационном файле sanctum. Этот параметр конфигурации определяет, какие домены будут поддерживать аутентификацию с «фиксацией» на основе файлов cookie сессии Laravel при выполнении запросов к вашему API.

Если вы обращаетесь к своему приложению через URL-адрес, содержащий порт (например, 127.0.0.1:8000), то вы должны убедиться, что указали номер порта вместе с доменом.

Посредник Sanctum

Затем вы должны указать Laravel, что входящие запросы от вашего SPA могут аутентифицироваться с использованием файлов cookie сеанса Laravel, при этом позволяя запросам третьих сторон или мобильных приложений аутентифицироваться с использованием токенов API. Этого можно легко добиться, вызвав метод посредника statefulApi в файле bootstrap/app.php вашего приложения:

->withMiddleware(function (Middleware $middleware) {
    $middleware->statefulApi();
})

CORS и Cookies

Если у вас возникли проблемы с аутентификацией вашего SPA, который выполняется на отдельном поддомене, вы, вероятно, неправильно сконфигурировали параметры CORS (совместное использование ресурсов между разными источниками) или cookie сессий.

Файл конфигурации config/cors.php по умолчанию не публикуется. Если вам нужно настроить параметры CORS Laravel, вам следует опубликовать полный файл конфигурации cors с помощью Artisan-команды config:publish:

php artisan config:publish cors

Затем, вам необходимо убедиться, что конфигурация CORS вашего приложения возвращает заголовок Access-Control-Allow-Credentials со значением True. Этого можно добиться, установив для параметра supports_credentials значение true в конфигурационном файле config/cors.php вашего приложения.

Кроме того, вы должны включить опции withCredentials и withXSRFToken в глобальном экземпляре axios вашего приложения. Обычно это делается в файле resources/js/bootstrap.js. Если вы не используете Axios для выполнения HTTP-запросов из вашего фронтенда, вы должны выполнить аналогичную настройку для вашего собственного HTTP-клиента:

axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;

Наконец, вы должны убедиться, что конфигурация домена cookie сессии вашего приложения поддерживает любой поддомен вашего корневого домена. Вы можете сделать это, добавив к домену префикс . в конфигурационном файле config/session.php вашего приложения:

'domain' => '.domain.com',

Выполнение аутентификации SPA

Защита от CSRF

Для аутентификации вашего SPA страница «входа» вашего SPA должна сначала сделать запрос к /sanctum/csrf-cookie для инициализации защиты от CSRF для приложения:

axios.get('/sanctum/csrf-cookie').then(response => {
    // Login...
});

Во время этого запроса Laravel установит cookie XSRF-TOKEN, содержащий текущий токен CSRF. Этот токен следует передавать в заголовке X-XSRF-TOKEN при последующих запросах, что некоторые клиентские библиотеки HTTP, такие, как Axios и Angular HttpClient, будут делать автоматически за вас. Если ваша HTTP-библиотека JavaScript не задает автоматически значение, то вам нужно будет вручную установить заголовок X-XSRF-TOKEN, чтобы он соответствовал значению XSRF-TOKEN cookie, установленному этим маршрутом.

Вход в систему

После того как защита от CSRF была инициализирована, вы должны сделать POST-запрос к маршруту /login вашего приложения Laravel. Этот маршрут /login может быть реализован вручную или с использованием пакета безголовой аутентификации, такого как Laravel Fortify.

Если запрос на вход будет успешным, то пользователь будет аутентифицирован, и последующие запросы к маршрутам вашего приложения будут автоматически аутентифицироваться через cookie сессии, которые приложение Laravel отправило клиентской стороне. Кроме того, поскольку ваше приложение уже сделало запрос к маршруту /sanctum/csrf-cookie, то последующие запросы должны автоматически получать защиту от CSRF, пока ваш HTTP-клиент JavaScript отправляет значение XSRF-TOKEN cookie в заголовке X-XSRF-TOKEN.

Конечно, если сессия вашего пользователя истекает из-за отсутствия активности, то последующие запросы к приложению Laravel могут получить ответ об ошибках 401 или 419 HTTP. В этом случае вы должны перенаправить пользователя на страницу входа в SPA.

Вы можете написать свою реализацию /login; однако, вы должны убедиться, что она аутентифицирует пользователя, используя стандартные службы аутентификации на основе сессии, которые предлагает Laravel. Обычно это означает использование охранника аутентификации web.

Защита маршрутов SPA

Чтобы защитить маршруты так, чтобы все входящие запросы аутентифицировались, вы должны назначить охранника аутентификации sanctum к вашим маршрутам API в вашем файле routes/api.php. Этот охранник гарантирует, что входящие запросы аутентифицируются либо как запросы с «фиксацией» на основе файлов cookie сессии, либо содержат действительный заголовок токена API, если запрос поступает от третьей стороны.

use Illuminate\Http\Request;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

Авторизация частных каналов вещания

Если вашему SPA необходимо аутентифицировать трансляцию по частным каналам или каналам присутствия, вам следует удалить запись channels из метода withRouting, содержащегося в файле bootstrap/app.php вашего приложения. Вместо этого вам следует вызвать метод withBroadcasting, чтобы указать правильного посредника для маршрутов широковещания вашего приложения:

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        // ...
    )
    ->withBroadcasting(
        __DIR__.'/../routes/channels.php',
        ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],
    )

Затем, чтобы запросы авторизации Pusher были успешными, вам нужно будет предоставить определение authorizer Pusher при инициализации Laravel Echo. Это позволит вашему приложению настроить Pusher для использования экземпляра axios, ориентированного на междоменные запросы:

window.Echo = new Echo({
    broadcaster: "pusher",
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    encrypted: true,
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                axios.post('/api/broadcasting/auth', {
                    socket_id: socketId,
                    channel_name: channel.name
                })
                .then(response => {
                    callback(false, response.data);
                })
                .catch(error => {
                    callback(true, error);
                });
            }
        };
    },
})

Аутентификация мобильного приложения

Вы также можете использовать токены Sanctum для аутентификации запросов вашего мобильного приложения к вашему API. Процесс аутентификации запросов мобильного приложения аналогичен аутентификации запросов стороннего API; однако, есть небольшие различия в том, как вы будете выдавать токены API.

Выдача токенов API мобильного приложения

Для начала создайте маршрут, который принимает электронную почту / имя пользователя, пароль и имя устройства, а затем обменивает эти учетные данные на новый токен Sanctum. «Имя устройства», присвоенное этой конечной точке, предназначено для информационных целей и может иметь любое желаемое значение. В общем, значение имени устройства должно быть именем, которое узнает пользователь, например «iPhone 12 Nuno».

Как правило, вы делаете запрос к конечной точке токена с экрана «входа в систему» вашего мобильного приложения. Конечная точка вернет токен API в виде простого текста, который затем может быть сохранен на мобильном устройстве и использован для выполнения дополнительных API-запросов:

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

Route::post('/sanctum/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
});

Когда мобильное приложение использует токен для запроса API к вашему приложению, оно должно передать токен в заголовке Authorization как Bearer-токен.

При выдаче токенов для мобильного приложения вы также можете указать полномочия токена.

Защита маршрутов API мобильного приложения

Как ранее было задокументировано, вы можете защитить маршруты так, чтобы все входящие запросы аутентифицировались, назначив маршрутам охранника аутентификации sanctum:

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

Отзыв токенов API мобильного приложения

Чтобы пользователи могли отзывать токены API, выданные для мобильных устройств, вы можете перечислить их по имени вместе с кнопкой «Отозвать» в разделе «Настройки учетной записи» пользовательского интерфейса веб-приложения, например. Когда пользователь нажимает кнопку «Отозвать», вы можете удалить токен из базы данных. Помните, что вы можете получить доступ к токенам API пользователя через отношения tokens трейта Laravel\Sanctum\HasApiTokens:

// Отзыв всех токенов ...
$user->tokens()->delete();

// Отзыв определенного токена ...
$user->tokens()->where('id', $tokenId)->delete();

Тестирование

Во время тестирования метод Sanctum::actingAs может использоваться для аутентификации пользователя и указания, какие полномочия должны быть предоставлены его токену:

use App\Models\User;
use Laravel\Sanctum\Sanctum;

test('task list can be retrieved', function () {
    Sanctum::actingAs(
        User::factory()->create(),
        ['view-tasks']
    );

    $response = $this->get('/api/task');

    $response->assertOk();
});
use App\Models\User;
use Laravel\Sanctum\Sanctum;

public function test_task_list_can_be_retrieved(): void
{
    Sanctum::actingAs(
        User::factory()->create(),
        ['view-tasks']
    );

    $response = $this->get('/api/task');

    $response->assertOk();
}

Если вы хотите предоставить токену все полномочия, то вы должны указать * в списке полномочий метода actingAs:

Sanctum::actingAs(
    User::factory()->create(),
    ['*']
);