API Key Authentication Implementation for Web Application

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

API Key Authentication for Web Applications

API keys are the simplest authentication mechanism for machine-to-machine interactions (server-to-server). No OAuth flow, no refresh tokens, no sessions. The client passes the key in a request header or query parameter, and the server validates it. Suitable for public APIs, partner integrations, and CLI tools.

Key Generation and Storage

The key must be sufficiently random—minimum 32 bytes:

// Generate key
$key = 'sk_' . bin2hex(random_bytes(32)); // sk_ + 64 hex = 67 characters
// Example: sk_a3f9b12e8c4d7e1f0a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5

// Never store the key in plaintext—only hash it
$hash = hash('sha256', $key);

DB::table('api_keys')->insert([
    'user_id'    => $userId,
    'name'       => $request->name,
    'key_prefix' => substr($key, 0, 8), // for display to user
    'key_hash'   => $hash,
    'scopes'     => json_encode(['read:articles', 'write:articles']),
    'last_used_at' => null,
    'expires_at' => now()->addYear(),
]);

// Show the key to the user ONLY ONCE—upon creation
return response()->json(['key' => $key], 201);

Store only the hash—if the database is breached, the keys are useless.

Key Verification

// ApiKeyAuth Middleware
public function handle(Request $request, Closure $next): Response
{
    $key = $request->bearerToken()  // Authorization: Bearer sk_...
        ?? $request->header('X-Api-Key') // X-Api-Key: sk_...
        ?? $request->query('api_key');   // ?api_key=sk_... (avoid in URL)

    if (!$key) {
        return response()->json(['error' => 'API key required'], 401);
    }

    $hash = hash('sha256', $key);
    $apiKey = ApiKey::where('key_hash', $hash)
        ->where(fn($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))
        ->first();

    if (!$apiKey) {
        return response()->json(['error' => 'Invalid or expired API key'], 401);
    }

    // Update last_used_at asynchronously to avoid slowing down requests
    dispatch(fn() => $apiKey->update(['last_used_at' => now()]))->afterResponse();

    $request->setUserResolver(fn() => $apiKey->user);
    $request->attributes->set('api_key', $apiKey);

    return $next($request);
}

Scopes (Permissions)

The key should have the minimum necessary permissions:

// Check scope in controller or Middleware
public function store(Request $request): JsonResponse
{
    $apiKey = $request->attributes->get('api_key');

    if (!in_array('write:articles', $apiKey->scopes ?? [])) {
        return response()->json(['error' => 'Insufficient scope'], 403);
    }

    // ...
}

The scope list is defined when the user creates the key (or a fixed set is assigned).

Security

Key transmission:

  • Only via HTTPS
  • Preferably in the Authorization: Bearer or X-Api-Key header
  • Not in the URL (appears in logs, browser history, referer)

Key rotation:

// Invalidate the old key when creating a new one
ApiKey::where('user_id', $userId)->where('name', $name)->delete();

Usage audit:

ApiKeyUsageLog::create([
    'api_key_id' => $apiKey->id,
    'ip'         => $request->ip(),
    'endpoint'   => $request->path(),
    'method'     => $request->method(),
    'status'     => null, // filled in Terminate middleware
]);

Rate limiting by key:

RateLimiter::for('api-key', function (Request $request) {
    $apiKey = $request->attributes->get('api_key');
    return Limit::perMinute($apiKey->rate_limit ?? 60)->by($apiKey->id);
});

User Interface in Dashboard

List of keys showing key_prefix (first 8 characters):

sk_a3f9b1...  "Production integration"    Last used: 2 hours ago             [Delete]
sk_c2d4e5...  "Staging webhook"           Never used                         [Delete]

Adding a "Show key" button is not possible—the key is not stored. Only recreate it.

Timeline

API keys table, verification middleware, UI for key creation/deletion, rate limiting by key: 1–2 days.