Complete Guide to Service Repository Pattern Vs Direct El...

Avatar
M

Maksudur Rahman

Software Engineer

124Views
5mRead
1Reactions

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=10080

Step 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 expired even for active sessions

    • Cause: The server timezone and client timestamp are out of sync, or the token was created before'expiration'was enabled in config/sanctum.php.

    • Fix: Ensure APP_TIMEZONE=UTC is configured across all servers and verify that expires_at is populated in the personal_access_tokens table.

  • Error: Call to undefined method PersonalAccessToken::expires_at

    • Cause: Database migration for Sanctum's expires_at column was not executed during upgrades.

    • Fix: Run php artisan migrate to ensure the expires_at timestamp column exists on personal_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.php with Bearer <token>authorization headers.


  • Performance & Security Best Practices

    1. Automate Expired Token Cleanup: Schedule Sanctum's pruning command in routes/console.php to delete stale tokens and keep the database fast: use Illuminate\Support\Facades\Schedule; Schedule::command('sanctum:prune-expired --hours=48')->daily();

    2. Use Sliding Expirations: Issue short-lived access tokens (e.g. 24 hours) combined with secure refresh token rotation to minimize token theft risk.

    3. 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

    Recommended Resources & Courses

    React to this article