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

Avatar
C

Callum Davies

Senior Backend Engineer••
335Views
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

    Connect with KoderSolution 🚀

    Master modern software engineering, scalable architectures, and advanced backend systems.

    What's your setup?How does your production stack handle background jobs? Are you using Redis with Horizon, or cloud queues like AWS SQS?

    Recommended Resources & Courses

    Partner Links
    Callum Davies
    C
    Written by•Senior Backend Engineer

    Callum Davies

    PHP & Laravel practitioner since v5.4. Big believer in thin controllers, explicit return types, and boring modular architecture that doesn't page you on weekends.

    React to this article