Skip to content

Instantly share code, notes, and snippets.

@atomjoy
Last active September 3, 2025 18:05
Show Gist options
  • Save atomjoy/e0c1929a4489519557eb0007d0852714 to your computer and use it in GitHub Desktop.
Save atomjoy/e0c1929a4489519557eb0007d0852714 to your computer and use it in GitHub Desktop.
Laravel sanctum multiple guards.

Laravel Sanctum Multi Guards

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(),
        ]);
    });
});

Multi guards

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.

Dodaj guardy

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),
    ],
],

Dodaj guardy sanctum.php

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'], 

Dodaj middleware

// 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
// ]);

Routes

<?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');
    });

Test

<?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);
    }
}

ExpiredToken Middleware

<?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);
    }
}

Only admin

<?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);
    }
}

Only user

<?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);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment