← Back to Developer Blog
💻 DeveloperMarch 5, 20269 min read

API Authentication: JWT vs Sessions vs API Keys

Building an API? Here's how to choose between JWT, sessions, and API keys without overcomplicating things.

By Raspib Technology Team

API Authentication: JWT vs Sessions vs API Keys

Building an API and confused about authentication?

JWT? Sessions? API keys? OAuth? Sanctum? Passport?

Here's what to actually use.

The Options

1. API Keys (Simplest)

// Generate key
$apiKey = Str::random(64);

// Store in database
User::create([
    'name' => 'Client App',
    'api_key' => hash('sha256', $apiKey),
]);

// Authenticate requests
if (hash('sha256', $request->header('X-API-Key')) === $user->api_key) {
    // Valid
}

Pros:

  • Simple to implement
  • Fast
  • No expiration handling

Cons:

  • Can't revoke easily
  • No user context
  • If leaked, valid forever

Use for: Server-to-server APIs, webhooks, internal services.

// Login returns JWT
public function login(Request $request)
{
    $credentials = $request->only('email', 'password');
    
    if (!$token = auth()->attempt($credentials)) {
        return response()->json(['error' => 'Unauthorized'], 401);
    }
    
    return response()->json(['token' => $token]);
}

// Use token in requests
// Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...

Pros:

  • Stateless (no database lookup)
  • Works across multiple servers
  • Contains user data

Cons:

  • Can't revoke (until expiry)
  • Larger than session IDs
  • Need refresh token strategy

Use for: Mobile apps, SPAs, microservices.

3. Sessions (Traditional)

// Login creates session
public function login(Request $request)
{
    if (Auth::attempt($credentials)) {
        $request->session()->regenerate();
        return response()->json(['success' => true]);
    }
}

// Session cookie sent automatically

Pros:

  • Easy to revoke
  • Smaller than JWT
  • Built into Laravel

Cons:

  • Requires database/Redis
  • Doesn't work well with mobile apps
  • CORS complications

Use for: Traditional web apps, admin panels.

What We Actually Use

For Mobile Apps: JWT with Refresh Tokens

// Login returns access + refresh token
public function login(Request $request)
{
    $user = User::where('email', $request->email)->first();
    
    if (!$user || !Hash::check($request->password, $user->password)) {
        return response()->json(['error' => 'Invalid credentials'], 401);
    }
    
    $accessToken = $user->createToken('access', ['*'], now()->addMinutes(15));
    $refreshToken = $user->createToken('refresh', ['refresh'], now()->addDays(30));
    
    return response()->json([
        'access_token' => $accessToken->plainTextToken,
        'refresh_token' => $refreshToken->plainTextToken,
        'expires_in' => 900, // 15 minutes
    ]);
}

// Refresh endpoint
public function refresh(Request $request)
{
    $user = $request->user();
    
    // Revoke old tokens
    $user->tokens()->delete();
    
    // Issue new tokens
    $accessToken = $user->createToken('access', ['*'], now()->addMinutes(15));
    $refreshToken = $user->createToken('refresh', ['refresh'], now()->addDays(30));
    
    return response()->json([
        'access_token' => $accessToken->plainTextToken,
        'refresh_token' => $refreshToken->plainTextToken,
    ]);
}

Why:

  • Short-lived access tokens (15 min) = secure
  • Long-lived refresh tokens (30 days) = good UX
  • Can revoke anytime

For Web Apps: Laravel Sanctum

// SPA authentication
Route::post('/login', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);
    
    if (Auth::attempt($request->only('email', 'password'))) {
        $request->session()->regenerate();
        return response()->json(['user' => Auth::user()]);
    }
    
    return response()->json(['error' => 'Invalid credentials'], 401);
});

Uses cookies. Simple. Works great with Next.js, React, Vue.

For Third-Party Integrations: API Keys

// Generate API key for client
$client = Client::create([
    'name' => 'Partner App',
    'api_key' => Str::random(64),
]);

// Middleware to check API key
public function handle($request, Closure $next)
{
    $apiKey = $request->header('X-API-Key');
    
    $client = Client::where('api_key', $apiKey)->first();
    
    if (!$client) {
        return response()->json(['error' => 'Invalid API key'], 401);
    }
    
    $request->merge(['client' => $client]);
    
    return $next($request);
}

Security Best Practices

1. Always Hash API Keys

// Don't store plain text
$user->api_key = $apiKey;  // Wrong!

// Hash it
$user->api_key = hash('sha256', $apiKey);  // Right!

2. Use HTTPS Only

// Force HTTPS in production
if (!$request->secure() && app()->environment('production')) {
    return redirect()->secure($request->getRequestUri());
}

3. Rate Limit Auth Endpoints

Route::middleware('throttle:5,1')->group(function () {
    Route::post('/login', [AuthController::class, 'login']);
    Route::post('/register', [AuthController::class, 'register']);
});

Prevents brute force attacks.

4. Rotate Tokens

// Expire access tokens quickly
$token = $user->createToken('access', ['*'], now()->addMinutes(15));

// Provide refresh mechanism
Route::post('/refresh', [AuthController::class, 'refresh']);

Real Project Examples

Project 1: Mobile Banking App

Used: JWT with refresh tokens

// Access token: 15 minutes
// Refresh token: 30 days
// Stored in secure storage on device

Results:

  • Security: High (short-lived tokens)
  • UX: Good (refresh automatic)
  • Scalability: Excellent (stateless)

Project 2: Admin Dashboard

Used: Laravel Sanctum with sessions

// Cookie-based authentication
// Works seamlessly with Next.js frontend

Results:

  • Security: High (can revoke instantly)
  • UX: Excellent (no token management)
  • Complexity: Low (built into Laravel)

Project 3: Partner API

Used: API keys with scopes

// Each partner gets unique key
// Keys have specific permissions
$client->scopes = ['read:orders', 'write:products'];

Results:

  • Security: Good (scoped access)
  • Management: Easy (revoke anytime)
  • Monitoring: Simple (track by key)

Common Mistakes

Mistake 1: JWT Without Refresh Tokens

// Bad: Long-lived JWT
$token = auth()->setTTL(43200)->attempt($credentials);  // 30 days

If token leaks, valid for 30 days. Can't revoke.

Fix: Short access token + refresh token.

Mistake 2: Storing Tokens in LocalStorage

// Bad: Vulnerable to XSS
localStorage.setItem('token', token);

// Better: HttpOnly cookie
// Set from backend, JavaScript can't access

Mistake 3: No Token Rotation

// Bad: Same token forever
$token = $user->createToken('app');

// Good: Expire and refresh
$token = $user->createToken('app', ['*'], now()->addMinutes(15));

Which Should You Use?

Use API Keys if:

  • Server-to-server communication
  • Webhooks
  • Internal services
  • Simple authentication needs

Use JWT if:

  • Mobile apps
  • Microservices
  • Need stateless auth
  • Multiple servers

Use Sessions if:

  • Traditional web app
  • Admin dashboard
  • Need instant revocation
  • Simple setup preferred

Implementation (Laravel Sanctum)

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
// app/Http/Kernel.php
'api' => [
    \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    'throttle:api',
],
// Login
public function login(Request $request)
{
    if (!Auth::attempt($request->only('email', 'password'))) {
        return response()->json(['error' => 'Invalid credentials'], 401);
    }
    
    $token = $request->user()->createToken('app')->plainTextToken;
    
    return response()->json(['token' => $token]);
}

// Protected route
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Bottom Line

Don't overcomplicate authentication.

Mobile app? JWT with refresh tokens.
Web app? Sessions or Sanctum.
Server-to-server? API keys.

Pick what fits your use case. Implement it properly. Move on.


Building secure APIs?

We build authenticated APIs for Nigerian businesses. Mobile backends, web APIs, integrations.

📞 WhatsApp: +234 708 711 0468
📧 info@raspibtech.com
📍 Lagos Island

Related:

Need Help with Your Project?

Let's discuss how Raspib Technology can help transform your business

Related Articles

API Authentication Guide - JWT vs Sessions vs API Keys