presseportale/app/Http/Middleware/RejectLegacyApiKeys.php
Kevin Adametz 39e13d6d84 security: Host-Whitelist & Legacy-API-Key-Übergabe härten (H5, M11)
- trustHosts erlaubt nur die konfigurierten Domains (plus localhost/IP
  für Health-Checks) und verwirft fremde Host-Header, sodass
  X-Forwarded-Host keine URL-Generierung mehr vergiften kann (H5).
- Legacy-API-Keys nur noch per Header (Bearer/X-ApiKey); die alte
  Query-/Body-Übergabe wird nicht mehr zur Auth akzeptiert, erhält aber
  weiterhin den 410-Migrationshinweis statt eines opaken 401 (M11).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:48:28 +00:00

88 lines
3 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Laravel\Sanctum\PersonalAccessToken;
use Symfony\Component\HttpFoundation\Response;
/**
* Legacy-API-Kompatibilität (Übernahme bestehender Integrationen,
* 06.07.2026): Die Altportale authentifizierten per `X-ApiKey`-Header.
* Importierte Legacy-Keys (legacy:import-api-keys)
* sind gültige Sanctum-Tokens — hier wird die alte Übergabeform in einen
* Bearer-Header übersetzt, damit `auth:sanctum` sie versteht. Unbekannte
* Keys erhalten weiterhin 410 mit Migrationshinweis.
*/
class RejectLegacyApiKeys
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$legacyKey = $this->legacyKey($request);
if ($legacyKey === null) {
// Kein Header-Key: ein in Query/Body übergebener Alt-Schlüssel wird
// NICHT für die Auth akzeptiert (Zugangsdaten dürfen nicht in URLs/
// Logs landen), erhält aber weiterhin den Migrationshinweis statt
// eines nichtssagenden 401 — sofern kein regulärer Bearer-Token da ist.
if (blank($request->bearerToken()) && $this->hasParameterKey($request)) {
return $this->migrationHintResponse();
}
return $next($request);
}
// Ein explizit gesetzter Bearer-Token hat Vorrang.
if (filled($request->bearerToken())) {
return $next($request);
}
if (PersonalAccessToken::findToken($legacyKey) !== null) {
$request->headers->set('Authorization', 'Bearer '.$legacyKey);
return $next($request);
}
return $this->migrationHintResponse();
}
/**
* Legacy-Schlüssel ausschließlich aus Headern lesen. Query- und
* Body-Parameter werden bewusst nicht akzeptiert, damit Zugangsdaten
* nicht in URLs, Proxy-Logs oder Referrer-Headern landen.
*/
private function legacyKey(Request $request): ?string
{
$key = trim((string) (
$request->header('X-ApiKey')
?: $request->header('X-Api-Key')
));
return $key === '' ? null : $key;
}
/**
* Prüft, ob ein Alt-Schlüssel per Query- oder Body-Parameter übergeben
* wurde. Wird nur genutzt, um den Migrationshinweis auszulösen — der Wert
* wird nie zur Authentifizierung verwendet.
*/
private function hasParameterKey(Request $request): bool
{
return filled($request->query('api_key')) || filled($request->input('api_key'));
}
private function migrationHintResponse(): Response
{
return response()->json([
'message' => 'This API key is not valid (anymore). Please generate a token in your customer account and send it as a Bearer token or X-ApiKey header.',
'migration_url' => url('/customer/tokens'),
'docs_url' => url('/docs/api/v1'),
], 410);
}
}