Sanctum loguje zawsze guardy z ustawień 'guard' => ['web', 'admin'] trzeba sprawdzać który user jest zalogowany (sanctum guard nie działają podwójne logowania, zawsze brany jest pierwszy guard) można to zmienić dodając auth:web,sanctum i auth:admin,sanctum middleware.
<?php
// Działa to z tokenem i guardami, nawet z podwójnie zalogowanymi userami SPA z testów!
// Użyj web guard a jak nie to sanctum guard
Route::group(['middleware' => ['auth:web,sanctum']], function () {
Route::get('/user', function (Request $request) {
// Only user (for sanctum required)
if (! Auth::user() instanceof User) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return response()->json([
'message' => 'Authenticated.',
'user' => Auth::user()->fresh(),
]);
});
});
// Użyj admin guard a jak nie to sanctum guard (lub tylko sanctum)
Route::group(['middleware' => ['auth:admin,sanctum']], function () {
Route::get('/admin/user', function (Request $request) {
// Only admin (for sanctum required)
if (! Auth::user() instanceof Admin) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return response()->json([
'message' => 'Authenticated.',
'user' => Auth::user()->fresh(),
]);
});
});
Zamiast multi guards użyj spatie permissions z rolami (admin, manager, etc...) z defaultowym guardem (nie trzeba tyle logowań pisać) lub przenieś panel admina i partnerów do subdomen i poblokuj dostęp z ip.
app.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
//'session_guard' => 'web',
//'cookie' => 'web_session',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
//'session_guard' => 'admin',
//'cookie' => 'admin_session',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
'admins' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL_ADMIN', App\Models\Admin::class),
],
],
Sanktum sprawsza którego usera zalogować z auth:sanctum middleware. To middleware wyłącza też działanie abilites i ability middleware dla SPA.
// SPA guards (w testach zawsze pierwszy będzie zalogowany)
'guard' => ['web', 'admin'],
// Sanctun SPA (required)
$middleware->statefulApi();
// Sanctum
$middleware->alias([
// Sanctum ability
'abilities' => CheckAbilities::class,
'ability' => CheckForAnyAbility::class,
// Middleware do sprawdzania instancji zalogowanego usera lub admina.
//'sanctum_web' => SanctumWeb::class,
//'sanctum_admin' => SanctumAdmin::class,
]);
// Sanctum API optional
// $middleware->api(prepend: [
// \App\Http\Middleware\Api\ExpiredToken::class
// ]);
<?php
use App\Models\Admin;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Laravel\Sanctum\PersonalAccessToken;
// Login admin spa
Route::get('/alogin', function () {
$user = Admin::first();
Auth::guard('admin')->login($user);
return response()->json([
'user' => $user
]);
});
// Login user spa
Route::get('/ulogin', function () {
$user = User::first();
Auth::guard('web')->login($user);
return response()->json([
'user' => $user
]);
});
// Login admin token
Route::get('/atoken', function () {
$user = Admin::first();
$bearer = $user->createToken('admin-token-mobile', ['admin-token'], now()->addYear())->plainTextToken;
return response()->json([
'message' => 'Authenticated.',
'token' => $bearer, // Bearer token
]);
})->name('atoken');
// Login user token
Route::get('/utoken', function () {
$user = User::first();
$bearer = $user->createToken('user-token-mobile', ['user-token'], now()->addYear())->plainTextToken;
return response()->json([
'message' => 'Authenticated.',
'token' => $bearer, // Bearer token
]);
})->name('utoken');
// Logged user
Route::middleware(['auth:web,sanctum'])
->name('api.')
->group(function () {
Route::get('/user', function (Request $request) {
if (! Auth::user() instanceof User) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return response()->json([
'message' => 'Authenticated.',
'user' => Auth::user()->fresh(),
]);
})->name('user');
});
// Logged admin
Route::middleware(['auth:admin,sanctum'])
->prefix('admin')
->name('api.admin.')
->group(function () {
Route::get('/user', function (Request $request) {
if (! Auth::user() instanceof Admin) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return response()->json([
'message' => 'Authenticated.',
'user' => Auth::user()->fresh(),
]);
})->name('user');
});
// Admin routes
Route::prefix('admin')->name('admin.')->group(function () {
Route::group(['middleware' => 'auth:sanctum'], function () {
// Auth admin api endpoints
});
});
// Sanctum middleware ignoruje abilities i ability middleware dla zapytań SPA
// bez bearer token i loguje usera z sessją z guardem tablicy guards (trzeba sprawdzać instanceof dla usera).
// Nie używać multi guards i abilities w middleware wykluczaja się.
// Można usunąć abilities:admin-token middlewate i dodać $user->tokenCan('admin-token') tylko dla tokenów.
Route::middleware(['auth:sanctum', 'abilities:admin-token'])
->name('api.')
->group(function () {
// Logged user
Route::get('/user/ability', function (Request $request) {
// Get token
$bearer = $request->bearerToken();
$token = PersonalAccessToken::findToken($bearer);
if ($token instanceof PersonalAccessToken) {
// $token->expires_at->isPast()
}
// Only admin
if (! Auth::user() instanceof Admin) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return response()->json([
'message' => 'Authenticated.',
'user' => Auth::user()->fresh(),
]);
})->name('user.ability');
});
<?php
namespace Tests\Feature;
// use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Admin;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
/**
* A basic test example.
*/
public function test_admin_login(): void
{
$this->seed();
// $token = $user->createToken('user-token-mobile', ['*'], now()->addYear())->plainTextToken;
// $response = $this->withToken($token)->getJson('/api/user');
// Login
$response = $this->getJson('/api/alogin');
$response->assertStatus(200);
$response = $this->getJson('/api/ulogin');
$response->assertStatus(200);
// Or
// $this->actingAs(Admin::first(), 'admin');
// Sanctum::actingAs(User::first(), ['user-token'], 'web');
// Sanctum::actingAs(User::first(), ['user-token'], 'web');
// Sanctum::actingAs(Admin::first(), ['admin-token'], 'admin');
// Sanctum::actingAs(Admin::first(), ['admin-token']);
// Sanctum::actingAs(Admin::first());
$response = $this->getJson('/api/user');
$response->assertStatus(200)->assertJson(['user' => ['name' => 'Test User']]);
$response = $this->getJson('/api/admin/user');
$response->assertStatus(200)->assertJson(['user' => ['name' => 'Test Admin']]);
$response = $this->getJson('/api/user/ability');
$response->assertStatus(401)->assertJson(['message' => 'Unauthorized.']);
// dd($response->decodeResponseJson());
}
/**
* A basic test example.
*/
public function test_user_login_token(): void
{
$this->seed();
$user = User::first();
$token = $user->createToken('mobile-token', ['*'], now()->addYear())->plainTextToken;
$response = $this->withToken($token)->getJson('/api/user');
$response->assertStatus(200)->assertJson(['user' => ['name' => 'Test User']]);
}
/**
* A basic test example.
*/
public function test_admin_login_token(): void
{
$this->seed();
$user = Admin::first();
$token = $user->createToken('mobile-token', ['*'], now()->addYear())->plainTextToken;
$response = $this->withToken($token)->getJson('/api/admin/user');
$response->assertStatus(200)->assertJson(['user' => ['name' => 'Test Admin']]);
}
/**
* A basic test example.
*/
public function test_admin_login_token_ok(): void
{
$this->seed();
$user = Admin::first();
$token = $user->createToken('mobile-admin-token', ['admin-token'], now()->addYear())->plainTextToken;
$response = $this->withToken($token)->getJson('/api/user/ability');
$response->assertStatus(200)->assertJson(['user' => ['name' => 'Test Admin']]);
}
/**
* A basic test example.
*/
public function test_admin_login_token_err(): void
{
$this->seed();
$user = Admin::first();
$token = $user->createToken('mobile-token', ['user-token'], now()->addYear())->plainTextToken;
$response = $this->withToken($token)->getJson('/api/user/ability');
$response->assertStatus(403);
}
}
<?php
namespace App\Http\Middleware\Api;
use Closure;
use Illuminate\Http\Request;
use Laravel\Sanctum\PersonalAccessToken;
/**
* Sanctum expired token middleware.
*
* Add middleware in bootstrap/app.php
* $middleware->api(prepend: [ \App\Http\Middleware\Api\ExpiredToken::class ]);
*/
class ExpiredToken
{
public function handle(Request $request, Closure $next)
{
$bearer = $request->bearerToken();
if ($bearer) {
$token = PersonalAccessToken::findToken($bearer);
if ($token instanceof PersonalAccessToken) {
if($token->expires_at && $token->expires_at->isPast()) {
$request->merge([
'token_expired' => $token->expires_at && $token->expires_at->isPast(),
'token_details' => $token
]);
return response()->json([
'message' => 'Expired Token.',
'token_expired' => $token->expires_at && $token->expires_at->isPast(),
'token_details' => $token
], 403);
}
}
}
return $next($request);
}
}
<?php
namespace App\Http\Middleware\Api;
use Closure;
use App\Models\Admin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Sanctum logged admin middleware.
*/
class SanctumAdmin
{
public function handle(Request $request, Closure $next)
{
if (! Auth::user() instanceof Admin) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return $next($request);
}
}
<?php
namespace App\Http\Middleware\Api;
use Closure;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Sanctum logged web middleware.
*/
class SanctumWeb
{
public function handle(Request $request, Closure $next)
{
if (! Auth::user() instanceof User) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return $next($request);
}
}