Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Mahmoud-Faisal's avatar

Sanctum multi auth

Hello, I am making a multi auth api using laravel breeze api and snactum as a base this api should be consumed by a mobile application using tokens and a SPA with react using sessions

I have 3 guards Ex. student, teacher and admin

What is the right way to do it?

0 likes
6 replies
jlrdw's avatar

using sessions

Then I suggest a regular web app.

Mahmoud-Faisal's avatar

@jlrdw @jussimannisto

Thank you for replying

Firstly I tried this https://laracasts.com/discuss/channels/laravel/sanctum-spa-multi-auth

Here is the code: in my ./config/auth.php

'guards' => [
        'web-api' => [
            'driver' => 'sanctum',
            'provider' => 'users',
        ],
        'admin-api' => [
            'driver' => 'sanctum',
            'provider' => 'admins',
        ],
],

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
],

In my ./routes/api.php

<?php

use App\Http\Controllers\Web\Auth\AuthenticatedTokenController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::prefix('v1/')->name('api.v1.')->group(function () {
    // Auth Routes
    Route::post('login', [AuthenticatedTokenController::class, 'store'])->name('mobile.login');
});

Route::middleware(['auth:web-api'])->prefix('v1/')->name('api.v1.')->group(function () {
    // Auth Routes
    Route::post('logout', [AuthenticatedTokenController::class, 'destroy'])->middleware('auth:web-api')->name('mobile.logout');

    // User Routes
    return [
            'message' => 'student page',
            'student' => $request->user()
    ];
});

I made another routes file for the admin ./routes/api-admin.php

<?php

use App\Http\Controllers\Admin\Auth\AuthenticatedTokenController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::prefix('v1/admin/')->name('api.v1.admin.')->group(function () {
    // Auth Routes
    Route::post('login', [AuthenticatedTokenController::class, 'store'])->name('mobile.login');
});

Route::middleware(['auth:admin-api'])->prefix('v1/admin/')->name('api.v1.admin.')->group(function () {
    // Auth Routes
    Route::post('logout', [AuthenticatedTokenController::class, 'destroy'])->middleware('auth:admin-api')->name('mobile.logout');

    // User Routes
    Route::get('/user', function (Request $request) {
        return [
            'message' => 'admin page',
            'admin' => $request->user()
        ];
    });
});

It's working fine using the tokens by testing using postman althout it seems it uses the guard web in both admin and student pages

However in the react app if I signed in with the student for example I can access the admin pages though it have the middleware auth:admin-api and I recieve this response

{
    "message": "admin page",
    "admin": {
        "id": 7,
        "image": "https:\/\/via.placeholder.com\/640x480.png\/001144?text=aut",
        "name": "Nyah Marquardt",
        "email": "[email protected]",
        "email_verified_at": "2024-02-17T20:52:04.000000Z",
        "status": 0,
        "grade": 6,
        "created_at": "2024-02-17T20:52:05.000000Z",
        "updated_at": "2024-02-17T20:52:05.000000Z"
    }
}

This is the student not the admin and if I use the admin guard like this

Route::get('/user', function (Request $request) {
        return [
            'message' => 'admin page',
            'admin' => Auth::guard('admin')->user()
        ];
});

It returns

{
    "message": "admin page",
    "admin": null
}

I made several tries I can also share like using both guards auth:admin,admin-api the admin uses a session driver but non of them seem to work

JussiMannisto's avatar

Sounds standard. You get a good starting point with the Breeze React scaffolding.

Mahmoud-Faisal's avatar
Mahmoud-Faisal
OP
Best Answer
Level 1

Here is how I managed to achieve it

Firstly I made 2 guards for every authenticatable table entity one for SPA using the session driver and one for the mobile with tokens using sanctum driver

My ./config/auth.php

'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'web-api' => [
            'driver' => 'sanctum',
            'provider' => 'users',
        ],
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
        'admin-api' => [
            'driver' => 'sanctum',
            'provider' => 'admins',
        ],
 ],

'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
        'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],
 ],

Then I use the admin guard for my SPA only routes and admin-api for the token routes and I use both together if I want the routes to be authenticated from either SPA using sessions or Mobile using token same for the web and web-api guards

So the middlewares are auth:admin, auth:admin-api and auth:admin,admin-api same for the web

My ./routes/api-admin.php

<?php

use App\Http\Controllers\Admin\Auth\AuthenticatedTokenController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::prefix('v1/admin/')->name('api.v1.admin.')->group(function () {
    // Auth Routes
    Route::post('login', [AuthenticatedTokenController::class, 'store'])->name('mobile.login');
});

Route::middleware(['auth:admin,admin-api'])->prefix('v1/admin/')->name('api.v1.admin.')->group(function () {
    // User Routes
    Route::get('/user', function (Request $request) {
        return Auth::user();
        return [
            'message' => 'admin page',
            'admin' => $request->user()
        ];
    });
});

// Auth Api Routes
Route::post('logout', [AuthenticatedTokenController::class, 'destroy'])->middleware('auth:admin-api')->name('mobile.logout');

Then I adjusted the guard in the sanctum config file to null ./config/sanctum.php

/*
    |--------------------------------------------------------------------------
    | Sanctum Guards
    |--------------------------------------------------------------------------
    |
    | This array contains the authentication guards that will be checked when
    | Sanctum is trying to authenticate a request. If none of these guards
    | are able to authenticate the request, Sanctum will use the bearer
    | token that's present on an incoming request for authentication.
    |
    */

    'guard' => null,

Then I added the admin guards to the guest and auth middlewares to the SPA auth routes like guest:admin

And Lastly this is my AuthenticatedTokenController

<?php

namespace App\Http\Controllers\Admin\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;

class AuthenticatedTokenController extends Controller
{
    /**
     * Handle an incoming authentication request.
     */
    public function store(LoginRequest $request)
    {
        Auth::shouldUse('admin');

        $request->validate([
            'device_name' => 'required',
        ]);

        $request->authenticate();

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

    /**
     * Destroy an authenticated session.
     */
    public function destroy(Request $request): Response
    {
        $request->user()->currentAccessToken()->delete();

        return response()->noContent();
    }
}
1 like

Please or to participate in this conversation.