- 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>
88 lines
3 KiB
PHP
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);
|
|
}
|
|
}
|