It started off as a normal morning, in my todo list was an entry for “tech stack research for the new product”. When I started this task, I decided to spend only one hour on this and compile my report or so I thought!
While researching I like to do a test deployment of the stack to see whether I can deploy it or not. The consensus is, if I can deploy it easily the team can do it more quickly.
The tech stack that I choose will be deployed with the following setup:
- Backend API to be developed with a framework
- Frontend Web & mobile apps will consume the API.
- Frontend Web App will not be deployed under the same domain.
After some searching on the web I shortlisted the following technologies:
- Laravel: A PHP framework used for building web applications with an elegant syntax, MVC architecture, and built-in features like authentication, routing, and Eloquent ORM.
- Django: A high-level Python web framework that emphasizes rapid development and security, following the “batteries-included” philosophy with built-in authentication, ORM, and admin panel.
- ExpressJS: A lightweight and flexible Node.js web framework for building APIs and web applications. It simplifies server-side development with minimal setup and middleware support.
- VueJS: A progressive JavaScript framework used for building user interfaces and single-page applications (SPAs). It’s known for its reactivity, component-based architecture, and easy learning curve.
- Nginx: A high-performance web server, reverse proxy, and load balancer. It’s used to serve static content, improve scalability, and handle high concurrent connections efficiently.
- MySQL: A relational database management system (RDBMS) that uses structured tables, SQL queries, and is widely used in applications requiring ACID compliance and structured data storage.
- MongoDB: A NoSQL database that stores data in flexible, JSON-like documents, making it well-suited for applications needing scalability, high performance, and unstructured data management.
For deployment a few of my test criteria were:
- How easily can I deploy the environment via Docker.
- Container size
- Database performance
- Framework performance
- Available packages for framework
- Framework compatibility with the database
- Authentication & Authorization complexity.
The combination of Laravel with MongoDB and the use of Sanctum for authentication and authorization took my whole day. So I decided to write about it with the hopes of helping others and for someone to suggest a better approach.
Assumption: You are running a php-fpm with composer, nginx, and mogodb containers.
Installing Laravel
To install Laravel we need to connect to the php-fpm container. After switching the current working directory to /var/www. Run the command below. If you would like other method for installing the Laravel framework, checkout their documentation.
composer create-project laravel/laravel my-project
Installing Sanctum
Now that Laravel is installed. We need to install Sanctum – a Laravel package that provides authentication and authorization for Single Page Applications (SPA), mobile applications, and simple, token based APIs.
php artisan install:api
This command will install Sanctum and publish other service provider components.
Installing MongoDB Package for Laravel
In order to use MongoDB with Laravel we need MongoDB-PHP extension and relevant package.
composer require mongodb/laravel-mongodb
Once the package is installed, it is time to further configure Laravel.
Configuration
The very first step is to open the class App\Models\User file and replace the Illuminate\Foundation\Auth\User class with MongoDB\Laravel\Auth\User and use HasApiToken trait if it is not already there.
use MongoDB\Laravel\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable,
...
Next we need to customize the PersonalAccessToken class of Sanctum to use MongoDB DocumentModel trait and identify the primary key. Create a file in the Models folder and add the following content.
<?php
namespace App\Models;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;
use MongoDB\Laravel\Eloquent\DocumentModel;
class PersonalAccessToken extends SanctumPersonalAccessToken
{
use DocumentModel;
protected $primaryKey = '_id';
}
After saving the file open the file App\Providers\AppServiceProvider and add the following line to the boot method.
use Laravel\Sanctum\Sanctum;
use App\Models\PersonalAccessToken;
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
Next we create a controller to issue an access token. Create a LoginController and add the following content
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
{
public function __invoke(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 response()->json([
'user' => $user->details,
'access_token' => $user->createToken($request->device_name)->plainTextToken
]);
}
}
Now we create two routes inside routes/api.php file one for login and another protected route to test token authorization.
Route::post('auth/token', LoginController::class);
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
});
To test the setup open tinker and create a User. After that open your favorite API testing app e.g. postman or thunder client for VS code and login as shown:

Copy the value of access_token and use it for the next request Authorization header Bearer token as shown:

Surprise!! Unauthorized… If it worked for you congratulations! You can continue your API development.
But if it didn’t like in my case, continue this article. This particular issue took me the whole day to solve and the solution was a simple one.
I used my own custom middleware.
Create a middleware using the command below:
php artisan make:middleware TokenAuthMiddleware
Next open the middleware file and add the code below:
<?php
namespace App\Http\Middleware;
use App\Models\PersonalAccessToken;
use Closure;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class TokenAuthMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$bearer = $request->bearerToken();
if (! $bearer ) throw new AuthorizationException("Unauthorized");
[$id, $tval] = explode('|', $bearer, 2);
$token = PersonalAccessToken::where('token', hash('sha256', $tval))->first();
$expiration = config('sanctum.expiration');
if (
$token &&
( ! $token->expires_at || ! $token->expires_at->isPast()) &&
(! $expiration || $token->created_at->gt(now()->subMinutes($expiration)))
) // check if the token is valid as well.
{
$token->forceFill(['last_used_at' => now()])->save();
$user = $token->tokenable;
Auth::login($user); // <---- this line resolved the issue.
return $next($request);
}
throw new AuthorizationException("Unauthorized");
}
}
Save the file and open the routes/api.php file again and change the middleware ‘auth:sanctum’ to TokenAuthMiddleware
Route::middleware(TokenAuthMiddleware::class)->get('/user', function (Request $request) {
return $request->user();
});
Now if you try again it will work. Feel free to post your solution in the comments section.