Аутентификация в Laravel -- это гибкая система, построенная на двух ключевых концепциях: guards (определяют, КАК пользователь аутентифицирован) и providers (определяют, ОТКУДА извлекается пользователь). Конфигурация находится в config/auth.php.
Конфигурация
// config/auth.php
return [
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'sanctum', // or 'token'
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// Database provider (without Eloquent)
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];
Guards и Providers
Guard -- КАК аутентифицировать
Session guard -- хранит состояние в сессии (для веб-приложений).
Token guard -- проверяет токен в запросе (для API).
Sanctum guard -- расширенная токенная аутентификация.
Provider -- ОТКУДА брать пользователя
Eloquent provider -- использует модель Eloquent.
Database provider -- использует прямые SQL-запросы.
// Using Auth facade with specific guard
use Illuminate\Support\Facades\Auth;
// Default guard
$user = Auth::user();
// Specific guard
$user = Auth::guard('api')->user();
// Check if user is authenticated
if (Auth::check()) {
// User is logged in
}
// Get user ID
$id = Auth::id();
Login и Logout
Ручная аутентификация
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
final class LoginController extends Controller
{
public function login(Request $request): RedirectResponse
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
// attempt() hashes the password and checks against the database
if (Auth::attempt($credentials)) {
// Regenerate session to prevent session fixation
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
return back()->withErrors([
'email' => 'Предоставленные учётные данные не соответствуют нашим записям.',
])->onlyInput('email');
}
public function logout(Request $request): RedirectResponse
{
Auth::logout();
// Invalidate session
$request->session()->invalidate();
// Regenerate CSRF token
$request->session()->regenerateToken();
return redirect('/');
}
}
Аутентификация с дополнительными условиями
// Additional conditions beyond email/password
if (Auth::attempt([
'email' => $email,
'password' => $password,
'is_active' => true, // Additional WHERE condition
])) {
// Only active users can log in
}
// Using a closure for complex conditions
if (Auth::attempt([
'email' => $email,
'password' => $password,
fn (Builder $query) => $query->whereNotNull('email_verified_at'),
])) {
// Only verified users
}
Аутентификация конкретного экземпляра пользователя
use App\Models\User;
$user = User::find(1);
// Login a specific user instance
Auth::login($user);
// Login and "remember" the user
Auth::login($user, remember: true);
// Login by user ID
Auth::loginUsingId(1);
Auth::loginUsingId(1, remember: true);
// One-time authentication (no session/cookie, for single request)
Auth::once(['email' => $email, 'password' => $password]);
Запоминание пользователей (Remember Me)
// With remember me checkbox
if (Auth::attempt($credentials, $request->boolean('remember'))) {
// User will be remembered
}
// Check if user was authenticated via "remember me" cookie
if (Auth::viaRemember()) {
// User was authenticated using remember token
}
Для работы "remember me" таблица users должна содержать колонку remember_token (varchar(100), nullable).
HTTP Basic Authentication
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
final class AuthenticateWithBasicAuth
{
public function handle($request, $next)
{
// Default: authenticate using 'email' field
return Auth::onceBasic() ?: $next($request);
}
}
// Or use built-in middleware
// Route::middleware('auth.basic')->group(function () { ... });
Stateless HTTP Basic Auth
// For API routes - no session cookie
Route::middleware('auth.basic.once')->group(function () {
// These routes use stateless HTTP Basic Auth
});
Кастомные Guards
<?php
declare(strict_types=1);
namespace App\Guards;
use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
final class ApiKeyGuard implements Guard
{
private ?Authenticatable $user = null;
public function __construct(
private readonly Request $request,
) {}
public function check(): bool
{
return $this->user() !== null;
}
public function guest(): bool
{
return ! $this->check();
}
public function user(): ?Authenticatable
{
if ($this->user !== null) {
return $this->user;
}
$apiKey = $this->request->header('X-API-Key');
if ($apiKey) {
$this->user = User::where('api_key', hash('sha256', $apiKey))
->first();
}
return $this->user;
}
public function id(): int|string|null
{
return $this->user()?->getAuthIdentifier();
}
public function validate(array $credentials = []): bool
{
$user = User::where('api_key', hash('sha256', $credentials['api_key'] ?? ''))
->first();
return $user !== null;
}
public function hasUser(): bool
{
return $this->user !== null;
}
public function setUser(Authenticatable $user): static
{
$this->user = $user;
return $this;
}
}
Регистрация кастомного Guard
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Guards\ApiKeyGuard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
final class AuthServiceProvider extends ServiceProvider
{
public function boot(): void
{
Auth::extend('api-key', function (Application $app, string $name, array $config) {
return new ApiKeyGuard(
$app['request'],
);
});
}
}
// config/auth.php
'guards' => [
'api-key' => [
'driver' => 'api-key',
'provider' => 'users',
],
],
Кастомные User Providers
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
final class LdapUserProvider implements UserProvider
{
public function retrieveById(mixed $identifier): ?Authenticatable
{
// Retrieve user from LDAP by ID
}
public function retrieveByToken(mixed $identifier, string $token): ?Authenticatable
{
// Retrieve user by remember token
}
public function updateRememberToken(Authenticatable $user, string $token): void
{
// Update remember token in storage
}
public function retrieveByCredentials(array $credentials): ?Authenticatable
{
// Retrieve user by credentials (e.g., email)
}
public function validateCredentials(Authenticatable $user, array $credentials): bool
{
// Validate password against LDAP
}
public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void
{
// Rehash password if needed
}
}
Laravel Sanctum
Sanctum предоставляет лёгкую систему аутентификации для SPA, мобильных приложений и простых API на токенах.
Установка
php artisan install:api
API Token Authentication
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
final class User extends Authenticatable
{
use HasApiTokens;
// ...
}
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
final class TokenController extends Controller
{
public function createToken(Request $request): JsonResponse
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
'device_name' => ['required', 'string'],
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Неверные учётные данные.'],
]);
}
// Create token with abilities (permissions)
$token = $user->createToken(
$request->device_name,
['orders:read', 'orders:write'] // Token abilities
);
return response()->json([
'token' => $token->plainTextToken,
]);
}
public function revokeTokens(Request $request): JsonResponse
{
// Revoke current token
$request->user()->currentAccessToken()->delete();
// Revoke all tokens
$request->user()->tokens()->delete();
// Revoke a specific token
$request->user()->tokens()->where('id', $tokenId)->delete();
return response()->json(['message' => 'Tokens revoked']);
}
}
Проверка abilities
Route::middleware('auth:sanctum')->get('/orders', function (Request $request) {
if ($request->user()->tokenCan('orders:read')) {
// Token has the 'orders:read' ability
}
});
// Or via middleware
Route::middleware(['auth:sanctum', 'ability:orders:read'])->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
});
// Any of the abilities
Route::middleware(['auth:sanctum', 'ability:orders:read,orders:write'])->group(function () {
// Token needs orders:read OR orders:write
});
SPA Authentication (Cookie-based)
Sanctum может аутентифицировать SPA через cookie-сессии без токенов.
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),
// Frontend SPA: first get CSRF cookie, then login
axios.get('/sanctum/csrf-cookie').then(() => {
axios.post('/login', {
email: '[email protected]',
password: 'password',
});
});
Sanctum vs Passport
| Характеристика | Sanctum | Passport |
|---|---|---|
| OAuth2 | Нет | Полная поддержка |
| SPA Auth | Да (cookie) | Нет (нужен token) |
| API Tokens | Простые, с abilities | Полные scope |
| Сложность | Минимальная | Значительная |
| Grant Types | Нет | Все OAuth2 grant types |
| Подходит для | SPA, мобильные, простые API | Сторонние приложения, OAuth2 серверы |
| Токены | Хранятся в БД (SHA-256) | JWT (опционально) |
Когда использовать Sanctum
- Собственное SPA + API
- Мобильное приложение + API
- Простая токенная аутентификация
- Нет нужды в OAuth2
Когда использовать Passport
- Стороннее приложение должно получить доступ к вашему API
- Нужен полный OAuth2 сервер
- Нужны grant types (authorization code, client credentials)
Middleware аутентификации
// Protect routes
Route::middleware('auth')->group(function () {
// Only authenticated users
});
// Specific guard
Route::middleware('auth:sanctum')->group(function () {
// Only users authenticated via Sanctum
});
// Multiple guards
Route::middleware('auth:web,api')->group(function () {
// Either web or API auth
});
// Guest only
Route::middleware('guest')->group(function () {
Route::get('/login', [LoginController::class, 'showForm']);
});
// Redirect if authenticated
Route::middleware('auth')->group(function () {
// Unauthenticated users are redirected to login
// Customizable in app/Http/Middleware/Authenticate.php
});
События аутентификации
use Illuminate\Auth\Events\Attempting;
use Illuminate\Auth\Events\Authenticated;
use Illuminate\Auth\Events\CurrentDeviceLogout;
use Illuminate\Auth\Events\Failed;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Auth\Events\OtherDeviceLogout;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Validated;
use Illuminate\Auth\Events\Verified;
// Listen for login events
Event::listen(Login::class, function (Login $event) {
Log::info('User logged in', [
'user_id' => $event->user->id,
'guard' => $event->guard,
'remember' => $event->remember,
]);
});
// Listen for failed attempts
Event::listen(Failed::class, function (Failed $event) {
Log::warning('Failed login attempt', [
'credentials' => $event->credentials['email'] ?? 'unknown',
'guard' => $event->guard,
]);
});
Тестирование аутентификации
<?php
declare(strict_types=1);
namespace Tests\Feature;
use App\Models\User;
use Tests\TestCase;
final class AuthenticationTest extends TestCase
{
public function test_user_can_login(): void
{
$user = User::factory()->create();
$response = $this->postJson('/login', [
'email' => $user->email,
'password' => 'password', // Factory default
]);
$this->assertAuthenticated();
$response->assertRedirect('/dashboard');
}
public function test_acting_as_user(): void
{
$user = User::factory()->create();
// Simulate authenticated user
$response = $this->actingAs($user)
->getJson('/api/profile');
$response->assertOk();
}
public function test_acting_as_with_guard(): void
{
$user = User::factory()->create();
// Authenticate on specific guard
$response = $this->actingAs($user, 'sanctum')
->getJson('/api/orders');
$response->assertOk();
}
public function test_sanctum_token(): void
{
$user = User::factory()->create();
$token = $user->createToken('test', ['orders:read']);
$response = $this->withHeader('Authorization', 'Bearer ' . $token->plainTextToken)
->getJson('/api/orders');
$response->assertOk();
}
}