Complete Guide to Service Repository Pattern Vs Direct El...
Maksudur Rahman
Software Engineer
Implementing Service Repository Pattern vs Direct Eloquent: Enterprise Trade-offs correctly in 2026 requires configuring explicit expiration boundaries, maintaining stateless token guards, and designing fallback token renewal workflows that prevent unexpected session disconnects for active users.
Architecture & Execution Lifecycle
Understanding how the request pipeline resolves state and evaluates token lifetimes ensures clean separation of concerns and robust security:
flowchart TD
A([Client Request]) --> B[Authentication Guard & Header Check]
B --> C{Token Valid & Not Expired?}
C -- No --> D[401 Unauthorized / Token Expired Response]
C -- Yes --> E[Resolve Authenticated User]
E --> F{Within Renewal Window?}
F -- Yes --> G[Issue Refreshed Token in Response Headers]
F -- No --> H[Proceed to Service Handler]
G --> H
H --> I[(Database / Cache State)]
I --> J([Standardized JSON Response])Step 1: Environment & Configuration Setup
Before customizing your application logic, configure your runtime settings. In Laravel applications, set your base expiration configuration in config/sanctum.php:
// config/sanctum.php
return [
'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) : ''
))),
'guard' => ['web'],
// Enforce token expiration in minutes (e.g., 7 days = 10080 minutes)
'expiration' => (int) env('SANCTUM_TOKEN_EXPIRATION_MINUTES', 10080),
'middleware' => [
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];Ensure your environment variables are set inside your.env file:
SANCTUM_TOKEN_EXPIRATION_MINUTES=10080Step 2: Service Layer & Implementation
Avoid putting token generation logic inside controllers. Create a dedicated service class to handle issuance, expiration calculation, and renewal:
namespace App\Services;
use InvalidArgumentException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ImplementationService
{
/**
* Execute transactional domain operation with strict type safety.
*/
public function execute(array $payload): array
{
if (empty($payload['primary_identifier'])) {
throw new InvalidArgumentException('Primary identifier is required to proceed.');
}
return DB::transaction(function () use ($payload) {
Log::info('Processing domain event with strict isolation', ['payload' => $payload]);
return [
'success' => true,
'status' => 'processed',
'executed_at' => now()->toIso8601String(),
];
});
}
}Step 3: Controller Wiring & Standardized Response
Expose clean API endpoints utilizing Form Requests for payload validation and standardized API JSON responses:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\Auth\SanctumTokenService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Maksudur\ApiResponse\ApiResponse;
class AuthTokenController extends Controller
{
public function __construct(
protected SanctumTokenService $tokenService
) {}
/**
* Extend or refresh existing token.
*/
public function refresh(Request $request): JsonResponse
{
$currentToken = $request->user()->currentAccessToken();
$refreshed = $this->tokenService->refreshTokenIfEligible($currentToken);
if (!$refreshed) {
return ApiResponse::success([
'status' => 'active',
'message' => 'Token remains valid and active.',
]);
}
return ApiResponse::success($refreshed, 'Token successfully refreshed with extended lifetime.');
}
}Common Errors & Troubleshooting Gotchas
Error:
Token has expiredeven for active sessionsCause: The server timezone and client timestamp are out of sync, or the token was created before
'expiration'was enabled inconfig/sanctum.php.Fix: Ensure
APP_TIMEZONE=UTCis configured across all servers and verify thatexpires_atis populated in thepersonal_access_tokenstable.
Error:
Call to undefined method PersonalAccessToken::expires_atCause: Database migration for Sanctum's
expires_atcolumn was not executed during upgrades.Fix: Run
php artisan migrateto ensure theexpires_attimestamp column exists onpersonal_access_tokens.
Error: 419 CSRF token mismatch on mobile/API clients
Cause: The request is hitting a stateful web route instead of a stateless API route.
Fix: Route the request through
routes/api.phpwithBearer <token>authorization headers.
Performance & Security Best Practices
Automate Expired Token Cleanup: Schedule Sanctum's pruning command in
routes/console.phpto delete stale tokens and keep the database fast:use Illuminate\Support\Facades\Schedule; Schedule::command('sanctum:prune-expired --hours=48')->daily();Use Sliding Expirations: Issue short-lived access tokens (e.g. 24 hours) combined with secure refresh token rotation to minimize token theft risk.
Bind Tokens to Device/User Agent: Store SHA-256 hashes of client headers or IP addresses to detect session hijacking attempts.
Frequently Asked Questions
Does changingexpirationinconfig/sanctum.phpaffect existing tokens?
Yes. Sanctum calculates expiration dynamically using created_at + expiration minutes unless an explicit expires_at value was recorded in the database record.
How do I grant specific abilities/scopes to expiring tokens?
Pass an array of abilities as the second argument to createToken():
$user->createToken('mobile-app', ['posts:read', 'posts:write'], $expiresAt);Can I set different expiration times for different devices (mobile vs desktop)?
Yes. Pass a custom Carbon expiration date directly into the third parameter of$user->createToken($name, $abilities, $expiresAt).
Next Steps & Related Resources
Explore related practical guides in our Articles Section or join fellow developers in the Community Forum.