Laravel Sanctum
- Вступ
- Встановлення
- Конфігурація
- Аутентифікація за допомогою API токенів
- Аутентифікація SPA
- Аутентифікація мобільного застосунку
- Тестування
Вступ
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 використовує аутентифікаційний guard Laravel web для цього. Це забезпечує переваги захисту від CSRF, аутентифікацію сесій, а також захист від витоку аутентифікаційних даних через XSS.
Sanctum намагатиметься аутентифікуватися за допомогою cookies лише тоді, коли вхідний запит надходить з вашого власного 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 дозволяє призначати "здібності" токенам. Здібності виконують подібну роль до "областей" в OAuth. Ви можете передати масив рядкових здібностей як другий аргумент методу createToken:
return $user->createToken('token-name', ['server:update'])->plainTextToken;
Коли обробляєте вхідний запит, автентифікований за допомогою Sanctum, ви можете визначити, чи має токен певну можливість, використовуючи методи tokenCan або tokenCant:
if ($user->tokenCan('server:update')) {
// ...
}
if ($user->tokenCant('server:update')) {
// ...
}
Token Ability Middleware
Sanctum також включає два middleware, які можуть бути використані для перевірки, що вхідний запит автентифікований за допомогою токена, якому надано певну можливість. Щоб почати, визначте наступні псевдоніми middleware у файлі 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 middleware може бути призначено до маршруту для перевірки того, що токен вхідного запиту має всі перелічені можливості:
Route::get('/orders', function () {
// Токен має як можливості "check-status", так і "place-orders"...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
The ability middleware може бути призначено до маршруту для перевірки, що токен вхідного запиту має принаймні одну з перелічених можливостей:
Route::get('/orders', function () {
// Токен має можливість "check-status" або "place-orders"...
})->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.
Захист маршрутів
Щоб захистити маршрути так, щоб усі вхідні запити повинні були бути автентифіковані, ви повинні прикріпити охорону автентифікації sanctum до ваших захищених маршрутів у файлах маршрутів routes/web.php та routes/api.php. Ця охорона забезпечить, що вхідні запити автентифіковані як такі, що мають стан, автентифіковані за допомогою cookie, або містять дійсний заголовок API токена, якщо запит надходить від третьої сторони.
Ви можете запитати, чому ми пропонуємо аутентифікувати маршрути у файлі 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 також існує для надання простого методу автентифікації односторінкових застосунків (SPAs), які потребують взаємодії з API на базі Laravel. Ці SPAs можуть знаходитися в тому ж репозиторії, що і ваш Laravel застосунок, або можуть бути в абсолютно окремому репозиторії.
Для цієї функції Sanctum не використовує жодних токенів. Натомість Sanctum використовує вбудовані в Laravel сервіси аутентифікації на основі сесій з використанням cookie. Такий підхід до аутентифікації забезпечує переваги захисту від CSRF, аутентифікацію сесій, а також захищає від витоку облікових даних аутентифікації через XSS.
Для автентифікації ваш SPA та API повинні мати один і той самий домен верхнього рівня. Однак, вони можуть бути розміщені на різних піддоменах. Крім того, ви повинні переконатися, що надсилаєте заголовок Accept: application/json і або заголовок Referer, або Origin разом із вашим запитом.
Конфігурація
Налаштування Ваших Власних Домашніх Доменів
Спочатку вам слід налаштувати, з яких доменів ваш SPA буде здійснювати запити. Ви можете налаштувати ці домени, використовуючи опцію конфігурації stateful у вашому конфігураційному файлі sanctum. Цей параметр конфігурації визначає, які домени будуть підтримувати "stateful" автентифікацію, використовуючи сесійні кукі Laravel при здійсненні запитів до вашого API.
Щоб допомогти вам налаштувати ваші власні stateful домени, Sanctum надає дві допоміжні функції, які ви можете включити в конфігурацію. По-перше, Sanctum::currentApplicationUrlWithPort() поверне поточний URL застосунку з змінної середовища APP_URL, а Sanctum::currentRequestHost() вставить заповнювач у список stateful доменів, який під час виконання буде замінено на хост з поточного запиту, щоб усі запити з тим самим доменом вважалися stateful.
Якщо ви отримуєте доступ до вашого застосунку через URL, який включає порт (127.0.0.1:8000), ви повинні переконатися, що включили номер порту разом з доменом.
Sanctum Middleware
Далі, ви повинні вказати Laravel, що вхідні запити від вашого SPA можуть автентифікуватися за допомогою сесійних cookie Laravel, при цьому дозволяючи запитам від третіх сторін або мобільних застосунків автентифікуватися за допомогою API токенів. Це можна легко виконати, викликавши метод statefulApi middleware у файлі bootstrap/app.php вашого застосунку:
->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})
CORS і Cookies
Якщо у вас виникають проблеми з автентифікацією у вашому застосунку з SPA, яке виконується на окремому піддомені, ви, ймовірно, неправильно налаштували ваші CORS (Cross-Origin Resource Sharing) або налаштування cookie сесії.
Файл конфігурації config/cors.php за замовчуванням не публікується. Якщо вам потрібно налаштувати параметри CORS у Laravel, ви повинні опублікувати повний файл конфігурації cors, використовуючи команду Artisan config:publish:
php artisan config:publish cors
Далі, ви повинні переконатися, що конфігурація CORS вашого застосунку повертає заголовок Access-Control-Allow-Credentials зі значенням True. Це можна зробити, встановивши опцію supports_credentials у файлі конфігурації вашого застосунку config/cors.php на true.
Крім того, ви повинні увімкнути опції 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',
Аутентифікація
Захист від CSRF
Щоб аутентифікувати ваш SPA, сторінка "login" вашого SPA повинна спочатку зробити запит до /sanctum/csrf-cookie кінцевої точки, щоб ініціалізувати захист CSRF для застосунку:
axios.get('/sanctum/csrf-cookie').then(response => {
// Login...
});
Під час цього запиту Laravel встановить cookie XSRF-TOKEN, що містить поточний CSRF токен. Цей токен повинен бути декодований з URL і переданий у заголовку X-XSRF-TOKEN у наступних запитах, що деякі бібліотеки HTTP клієнтів, такі як Axios і Angular HttpClient, зроблять автоматично для вас. Якщо ваша JavaScript бібліотека HTTP не встановлює значення для вас, вам потрібно вручну встановити заголовок X-XSRF-TOKEN так, щоб він відповідав декодованому з URL значенню cookie XSRF-TOKEN, яке встановлюється цим маршрутом.
Вхід
Після ініціалізації захисту CSRF, ви повинні зробити запит POST до маршруту /login вашого Laravel застосунку. Цей маршрут /login може бути реалізований вручну або з використанням пакету безголової автентифікації, такого як Laravel Fortify.
Якщо запит на вхід успішний, ви будете автентифіковані, і наступні запити до маршрутів вашого застосунку автоматично будуть автентифіковані через сесійну cookie, яку застосунок Laravel видав вашому клієнту. Крім того, оскільки ваш застосунок вже зробив запит до маршруту /sanctum/csrf-cookie, наступні запити повинні автоматично отримувати захист CSRF, доки ваш JavaScript HTTP клієнт надсилає значення cookie XSRF-TOKEN у заголовку X-XSRF-TOKEN.
Звичайно, якщо сесія вашого користувача закінчується через відсутність активності, наступні запити до Laravel-застосунку можуть отримати відповідь з HTTP-помилкою 401 або 419. У цьому випадку, ви повинні перенаправити користувача на сторінку входу вашого SPA.
Ви можете створити власну кінцеву точку /login; однак, ви повинні переконатися, що вона автентифікує користувача, використовуючи стандартні сесійні служби автентифікації, які надає Laravel. Зазвичай це означає використання аутентифікаційного захисника web.
Захист маршрутів
Щоб захистити маршрути так, щоб усі вхідні запити повинні були бути автентифіковані, ви повинні прикріпити охорону автентифікації sanctum до ваших API маршрутів у файлі routes/api.php. Ця охорона забезпечить, що вхідні запити автентифіковані як або станні автентифіковані запити з вашого SPA, або містять дійсний заголовок API токена, якщо запит від третьої сторони:
use Illuminate\Http\Request;
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
Авторизація приватних каналів трансляції
Якщо вашому SPA потрібно автентифікуватися з приватними / присутніми каналами трансляції, вам слід видалити запис channels з методу withRouting, що міститься у файлі bootstrap/app.php вашого застосунку. Натомість, вам слід викликати метод withBroadcasting, щоб ви могли вказати правильний middleware для маршрутів трансляції вашого застосунку:
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
// ...
)
->withBroadcasting(
__DIR__.'/../routes/channels.php',
['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],
)
Далі, щоб запити авторизації Pusher були успішними, вам потрібно надати власний Pusher authorizer при ініціалізації 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 Нуну".
Зазвичай ви надсилатимете запит до кінцевої точки токена з екрана "вхід" вашого мобільного застосунку. Кінцева точка поверне 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.
Коли ви видаєте токени для мобільного застосунку, ви також можете вказати можливості токена.
Захист маршрутів
Як було задокументовано раніше, ви можете захистити маршрути, щоб усі вхідні запити повинні були бути автентифіковані, прикріпивши до маршрутів охорону автентифікації sanctum:
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('auth:sanctum');
Відкликання токенів
Щоб дозволити користувачам відкликати 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(),
['*']
);
