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.
Table of Contents
- The Options
- 1. API Keys
- 2. JWT
- 3. Sessions
- What We Actually Use
- For Mobile Apps: JWT with Refresh Tokens
- For Web Apps: Laravel Sanctum
- For Third-Party Integrations: API Keys
- Security Best Practices
- 1. Always Hash API Keys
- 2. Use HTTPS Only
- 3. Rate Limit Auth Endpoints
- 4. Rotate Tokens
- Real Project Examples
- Common Mistakes
- Mistake 1: JWT Without Refresh Tokens
- Mistake 2: Storing Tokens in LocalStorage
- Mistake 3: No Token Rotation
- Which Should You Use?
- Use API Keys if:
- Use JWT if:
- Use Sessions if:
- Implementation
- Bottom Line
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.
2. JWT (Most Popular)
// 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
Laravel 11: What Changed and Why You Should Care
Laravel 11 is out. Slimmer structure, better performance, and features that actually save time. Here's what matters.
Read more →Laravel 12: The Upgrade You've Been Waiting For
Laravel 12 brings major improvements. Here's what changed and why it matters for your projects.
Read more →Next.js 15: The Features That Actually Matter
Next.js 15 changed a lot. Here's what affects your projects, what breaks, and when to upgrade.
Read more →