Laravel-Sicherheitspraktiken
Umfassende Sicherheitsrichtlinien für Laravel-Anwendungen zum Schutz vor häufigen Sicherheitslücken.
Wann zu aktivieren
- Einrichten der Laravel-Authentifizierung und -Autorisierung (Sanctum, Passport, Jetstream, Breeze)
- Implementieren von Benutzerrollen, -berechtigungen und -richtlinien
- Konfigurieren von Produktionssicherheitseinstellungen und Umgebungsvariablen
- Überprüfen von Laravel-Anwendungen auf Sicherheitslücken
- Bereitstellen von Laravel-Anwendungen in der Produktion
- Schreiben sicherer Eloquent-Abfragen und Migrationen
Produktionskonfiguration
Wesentliche Produktionseinstellungen
// config/app.php
'env' => env('APP_ENV', 'production'),
'debug' => (bool) env('APP_DEBUG', false), // KRITISCH: Niemals true in Produktion
'key' => env('APP_KEY'), // Muss gesetzt sein: php artisan key:generate
// config/session.php
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax',
// APP_KEY beim Booten überprüfen
// bootstrap/app.php oder in einem Service Provider
if (empty(config('app.key'))) {
throw new RuntimeException('APP_KEY ist nicht gesetzt. Führen Sie aus: php artisan key:generate');
}
Sicherheit der Umgebungsdatei
# .env NIEMALS in die Versionskontrolle einchecken
# .gitignore enthält standardmäßig bereits .env
# Stattdessen .env.example mit Platzhaltern verwenden
DB_PASSWORD=
APP_KEY=
SANCTUM_TOKEN_PREFIX=
# Erforderliche Variablen beim Booten validieren
// In AppServiceProvider::boot()
$requiredKeys = ['app.key', 'database.connections.mysql.database', 'database.connections.mysql.username'];
foreach ($requiredKeys as $key) {
if (empty(config($key))) {
throw new RuntimeException("Fehlender erforderlicher Konfigurationsschlüssel: {$key}");
}
}
HTTPS-Erzwingung
// AppServiceProvider::boot() oder Middleware
if (app()->environment('production')) {
URL::forceScheme('https');
request()->server->set('HTTPS', 'on');
}
// config/app.php für vertrauenswürdige Proxies (Load Balancer)
// Bestimmte IP-Bereiche verwenden – * vertraut allen, was X-Forwarded-*-Spoofing ermöglicht
// AWS: '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'
'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12'],
// HTTPS in Produktion über Middleware erzwingen
// app/Http/Middleware/ForceHttps.php
public function handle($request, Closure $next)
{
if (!$request->secure() && app()->environment('production')) {
return redirect()->secure($request->getRequestUri());
}
return $next($request);
}
Authentifizierung
Sanctum (API-Token-Authentifizierung)
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
env('APP_URL') ? ',' . parse_url(env('APP_URL'), PHP_URL_HOST) : ''
)));
'expiration' => 60 * 24, // Token-Ablauf in Minuten (null = nie)
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
// Tokens mit Fähigkeiten ausstellen
$token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;
// Fähigkeiten auf Routen validieren
Route::middleware('auth:sanctum')->group(function () {
Route::get('/orders', function () {
// Benutzer muss 'read'-Fähigkeit haben
abort_unless(Auth::user()->tokenCan('read'), 403);
// ...
})->middleware('abilities:read');
Route::post('/orders', function () {
// Benutzer muss 'write'-Fähigkeit haben
abort_unless(Auth::user()->tokenCan('write'), 403);
// ...
})->middleware('abilities:write');
});
Passwortsicherheit
// config/hashing.php
// Standard ist bcrypt. Argon2id ist stärker.
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12), // Erhöhen für stärkeres Hashing
],
'argon' => [
'memory' => 65536,
'threads' => 4,
'time' => 4,
],
// Passwortvalidierung in RegisterRequest
public function rules(): array
{
return [
'password' => [
'required',
'confirmed',
Password::min(12)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised(), // Überprüft haveibeenpwned
],
];
}
// Login-Versuche ratenbegrenzen
// App\Http\Controllers\Auth\AuthenticatedSessionController
protected function authenticated(Request $request, $user)
{
if ($user->wasRecentlyLockedOut()) {
// Benutzer über verdächtigen Login benachrichtigen
$user->notify(new SuspiciousLoginNotification($request->ip()));
}
}
Session-Verwaltung
// config/session.php
'driver' => env('SESSION_DRIVER', 'database'), // database/redis > file
'lifetime' => env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
'encrypt' => env('SESSION_ENCRYPT', false),
// Session bei Login regenerieren
// App\Http\Controllers\Auth\AuthenticatedSessionController
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate(); // KRITISCH: verhindert Session Fixation
return redirect()->intended(RouteServiceProvider::HOME);
}
// Session bei Logout ungültig machen
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
Autorisierung
Gates
// App\Providers\AuthServiceProvider
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('update-post', function (User $user, Post $post): bool {
return $user->id === $post->user_id;
});
Gate::define('publish-post', function (User $user): bool {
return $user->role === 'editor' || $user->role === 'admin';
});
// before() für Super-Admin-Überschreibung verwenden
Gate::before(function (User $user, string $ability): ?bool {
if ($user->role === 'super-admin') {
return true; // Gewährt alle Fähigkeiten
}
return null; // Fällt auf normale Prüfungen zurück
});
}
// Verwendung in Controllern
public function update(Request $request, Post $post): RedirectResponse
{
Gate::authorize('update-post', $post);
// Oder: $this->authorize('update-post', $post);
// Oder: abort_unless(Auth::user()->can('update-post', $post), 403);
// ...
}
Policies
// App\Policies\PostPolicy
class PostPolicy
{
use HandlesAuthorization;
public function viewAny(?User $user): bool
{
return true; // Öffentliche Auflistung
}
public function view(?User $user, Post $post): bool
{
return $post->is_published || ($user && $user->id === $post->user_id);
}
public function create(User $user): bool
{
return $user->hasVerifiedEmail(); // Muss E-Mail bestätigen
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id && $post->created_at->diffInDays(now()) <= 30;
}
public function restore(User $user, Post $post): bool
{
return $user->role === 'admin';
}
public function forceDelete(User $user, Post $post): bool
{
return $user->role === 'super-admin';
}
}
// In AuthServiceProvider registrieren
protected $policies = [
Post::class => PostPolicy::class,
];
// Controller-Verwendung
public function show(Post $post): View
{
$this->authorize('view', $post);
return view('posts.show', compact('post'));
}
// Blade-Verwendung
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Bearbeiten</a>
@endcan
@cannot('update', $post)
<span>Sie können diesen Beitrag nicht bearbeiten</span>
@endcannot
Middleware-Autorisierung
// Middleware in Routen verwenden
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
Route::get('/posts/create', [PostController::class, 'create'])
->middleware('can:create,App\Models\Post');
// Benutzerdefinierte Autorisierungs-Middleware
// app/Http/Middleware/CheckRole.php
class CheckRole
{
public function handle(Request $request, Closure $next, string $role): mixed
{
if (!$request->user() || $request->user()->role !== $role) {
abort(403, 'Unautorisiert. Dieser Bereich erfordert die Rolle: ' . $role);
}
return $next($request);
}
}
// In Kernel registrieren
protected $routeMiddleware = [
'role' => \App\Http\Middleware\CheckRole::class,
];
// Routenverwendung
Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/admin', [AdminController::class, 'index']);
});
Eloquent-Sicherheit
Mass Assignment-Schutz
// SCHLECHT: $guarded = [] erlaubt Massenzuweisung für ALLE Spalten
// NIEMALS $guarded = [] in Produktion verwenden
// GUT: Fillable-Attribute auf die Whitelist setzen
final class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'phone',
'avatar',
];
// NIEMALS 'role', 'is_admin', 'is_verified' hier hinzufügen
}
// GUT: Explizit steuern, welche Felder in Requests befüllt werden können
public function store(StoreUserRequest $request): RedirectResponse
{
$user = User::create($request->safe()->only([
'name', 'email', 'phone', 'avatar'
]));
// $request->safe() verwendet nur validierte Daten
// $request->only() ist ohne Validierungsregeln NICHT sicher
}
// SCHLECHT: Benutzer direkt mit Request-Daten erstellen
User::create($request->all()); // ANGREIFBAR gegen Mass Assignment!
// BESSER: DTOs für die Erstellung verwenden
$user = User::create($request->validated()); // Nur validierte Felder
SQL-Injection-Prävention
// GUT: Eloquent parametrisiert Abfragen automatisch
User::where('email', $userInput)->first();
User::whereRaw('email = ?', [$userInput])->first();
// GUT: Query Builder parametrisiert ebenfalls
DB::table('users')->where('email', $userInput)->first();
DB::select('SELECT * FROM users WHERE email = ?', [$userInput]);
// SCHLECHT: Rohe String-Interpolation
DB::select("SELECT * FROM users WHERE email = '{$userInput}'"); // ANGREIFBAR!
User::whereRaw("email = '{$userInput}'")->first(); // ANGREIFBAR!
// SCHLECHT: whereRaw/orderByRaw mit unescaptem Input
User::orderByRaw($userInput); // ANGREIFBAR!
User::groupByRaw($userInput); // ANGREIFBAR!
// SCHLECHT: DB::statement mit Verkettung
DB::statement("INSERT INTO users (email) VALUES ('{$userInput}')"); // ANGREIFBAR!
Attribut-Casting
final class User extends Authenticatable
{
protected $casts = [
'email_verified_at' => 'datetime',
'is_admin' => 'boolean', // Cast zu Boolean verhindert String-Injection
'settings' => 'array', // Automatisch json_encode/json_decode
'metadata' => 'encrypted:array', // Laravel 11+ verschlüsseltes Casting
'password' => 'hashed', // Laravel 10+ automatisches Hashing beim Setzen
];
}
Modell-Sicherheit
final class User extends Authenticatable
{
// Vertrauliche Attribute vor JSON/API-Antworten verbergen
protected $hidden = [
'password',
'remember_token',
'two_factor_secret',
'two_factor_recovery_codes',
];
// Nur sichere berechnete Attribute anhängen
protected $appends = ['full_name']; // sicher
// NIEMALS vertrauliche berechnete Daten anhängen
}
final class Post extends Model
{
// Global Scope zum Filtern von Soft-Deleted-Datensätzen
use SoftDeletes;
// N+1 verhindern, indem Lazy Loading eingeschränkt wird (optionaler Strict-Modus)
// AppServiceProvider::boot()
// Model::preventLazyLoading(!app()->isProduction());
}
CSRF-Schutz
Standard-Schutz
// Laravel CSRF ist standardmäßig über die Middleware VerifyCsrfToken aktiviert
// app/Http/Kernel.php (protected $middlewareGroups['web'])
// Alle POST/PUT/PATCH/DELETE-Formulare müssen @csrf enthalten
<form method="POST" action="/posts">
@csrf
<input type="text" name="title">
<button type="submit">Erstellen</button>
</form>
Routen ausschließen (Sorgfältig)
// app/Http/Middleware/VerifyCsrfToken.php
class VerifyCsrfToken extends Middleware
{
// Nur Routen ausschließen, die externen CSRF-Schutz haben (Webhooks usw.)
protected $except = [
'stripe/*', // Stripe-Webhooks verwenden eigene Signaturprüfung
// Blankett 'api/*' vermeiden — zustandsbehaftete Sanctum-Routen benötigen CSRF.
// Nur spezifische zustandslose Webhook/Endpoint-Routen ausschließen.
];
}
CSRF mit JavaScript
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
// Axios-Beispiel (Laravel wird mit Axios ausgeliefert)
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector(
'meta[name="csrf-token"]'
).getAttribute('content');
// Fetch-Beispiel
fetch('/posts', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
</script>
XSS-Prävention
Blade-Template-Sicherheit
{{-- SICHER: Automatisch von Blade escapt --}}
{{ $userInput }}
{{-- GEFÄHRLICH: Rohe Ausgabe — NIEMALS mit Benutzereingaben verwenden --}}
{!! $userInput !!}
{{-- SICHER: {!! !!} nur mit vertrauenswürdigen Inhalten verwenden, die Sie kontrollieren --}}
{!! $trustedHtmlFromYourServer !!}
{{-- GUT: Spezifische Escaping-Direktiven verwenden --}}
@js($data) {{-- JSON für JavaScript encodieren --}}
@json($data) {{-- JSON in Templates encodieren --}}
{{-- SCHLECHT: Direkte Benutzereingabe in rohem HTML --}}
<div>{!! $user->bio !!}</div> {{-- ANGREIFBAR, wenn der Benutzer die Bio bereitstellt --}}
Sichere HTML-Verarbeitung
// Wenn Sie etwas HTML erlauben müssen, verwenden Sie einen Whitelist-Ansatz
use HTMLPurifier; // Erfordert: composer require ezyang/htmlpurifier
public function sanitizeHtml(string $dirty): string
{
$config = \HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,i,a[href],ul,ol,li,br');
$config->set('URI.AllowedSchemes', ['http', 'https', 'mailto']);
$purifier = new \HTMLPurifier($config);
return $purifier->purify($dirty);
}
// In Blade:
<div>{!! $sanitizedHtml !!}</div> {{-- Jetzt sicher, da es bereinigt wurde --}}
Content Security Policy (CSP)
// CSP-Header über Middleware setzen
// app/Http/Middleware/ContentSecurityPolicy.php
class ContentSecurityPolicy
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('Content-Security-Policy', implode('; ', [
"default-src 'self'",
"script-src 'self' 'nonce-" . $this->generateNonce() . "' https://trusted-cdn.com",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https://images.example.com",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://api.example.com",
"frame-src 'none'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
]));
return $response;
}
protected function generateNonce(): string
{
return base64_encode(random_bytes(16));
}
}
// In Kernel registrieren
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\ContentSecurityPolicy::class,
],
];
API-Sicherheit
Ratenbegrenzung
// app/Providers/RouteServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
protected function configureRateLimiting(): void
{
// API-Ratenbegrenzung
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Login-Ratenbegrenzung
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->input('email') . '|' . $request->ip());
});
// Spezifische Ratenbegrenzung für sensible Endpunkte
RateLimiter::for('password-reset', function (Request $request) {
return Limit::perHour(3)->by($request->input('email'));
});
}
// In Routen verwenden
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::apiResource('orders', OrderController::class);
});
// Benutzerdefinierte Ratenbegrenzungs-Antwort
public function handle(Request $request, Closure $next, $limiterName = 'api'): mixed
{
$response = $next($request);
if (RateLimiter::tooManyAttempts($limiterName, 60)) {
$retryAfter = RateLimiter::availableIn($limiterName);
return response()->json([
'message' => 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.',
'retry_after' => $retryAfter,
], 429)
->header('Retry-After', $retryAfter);
}
return $response;
}
CORS-Konfiguration
// config/cors.php (Laravel 7+)
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'], // Oder bestimmte Methoden
'allowed_origins' => env('CORS_ALLOWED_ORIGINS')
? explode(',', env('CORS_ALLOWED_ORIGINS'))
: ['https://example.com'], // NIEMALS '*' in Produktion
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => ['X-Pagination-Total', 'X-Pagination-Page'],
'max_age' => 0,
'supports_credentials' => true, // Für Sanctum zustandsbehaftete Authentifizierung
];
Sanctum-Token-Fähigkeiten
// Token mit granularen Fähigkeiten definieren
// app/Providers/AuthServiceProvider.php
use Laravel\Sanctum\Sanctum;
public function boot(): void
{
Sanctum::authenticateAccessTokenUsing(function ($token, $abilities) {
// Benutzerdefinierte Token-Überprüfungslogik
if (in_array('*', $abilities)) {
return true; // Vollzugriff
}
return collect($abilities)->contains(function ($ability) use ($token) {
return $token->can($ability);
});
});
}
// Abilities für verschiedene Benutzerrollen definieren
class TokenAbility extends Enum
{
case READ = 'read';
case WRITE = 'write';
case ADMIN = 'admin';
case MANAGE_USERS = 'manage-users';
}
// Token mit bestimmten Fähigkeiten ausstellen
$token = $user->createToken(
'mobile-app',
[TokenAbility::READ->value, TokenAbility::WRITE->value]
);
// Middleware zum Überprüfen mehrerer Fähigkeiten
// app/Http/Middleware/CheckAbilities.php
class CheckAbilities
{
public function handle(Request $request, Closure $next, ...$abilities): mixed
{
if (!$request->user() || !$request->user()->currentAccessToken()) {
abort(401, 'Nicht authentifiziert.');
}
foreach ($abilities as $ability) {
if (!$request->user()->tokenCan($ability)) {
abort(403, "Fehlende Fähigkeit: {$ability}");
}
}
return $next($request);
}
}
Datenvalidierung
// Form Request-Validierung
// app/Http/Requests/StoreOrderRequest.php
class StoreOrderRequest extends FormRequest
{
public function authorize(): bool
{
return true; // Oder benutzerdefinierte Autorisierungslogik
}
public function rules(): array
{
return [
'items' => ['required', 'array', 'min:1', 'max:50'],
'items.*.product_id' => ['required', 'exists:products,id'],
'items.*.quantity' => ['required', 'integer', 'min:1', 'max:100'],
'shipping_address' => ['required', 'string', 'max:500'],
'payment_method' => ['required', 'in:credit_card,paypal,stripe'],
];
}
public function messages(): array
{
return [
'items.*.product_id.exists' => 'Eines der Produkte existiert nicht.',
'items.max' => 'Sie können maximal 50 Artikel pro Bestellung haben.',
];
}
}
Bereitstellungssicherheit
Umgebungsvariablen
# Erforderliche Variablen in der Produktion
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:generiertenSchlüsselHier
DB_HOST=127.0.0.1
DB_DATABASE=production_db
DB_USERNAME=secure_user
DB_PASSWORD=starkesPasswort
SESSION_SECURE_COOKIE=true
CORS_ALLOWED_ORIGINS=https://example.com
# NIEMALS vertrauliche Werte wie API-Schlüssel hardcodieren
# Umgebungsvariablen von einem sicheren Speicher verwenden (z. B. AWS Secrets Manager)
Dateiberechtigungen
# Sichere Dateiberechtigungen für Laravel
# storage/ und bootstrap/cache/ müssen beschreibbar sein
chmod -R 775 storage bootstrap/cache
chmod -R 750 public
chmod 600 .env
# Eigentümer einstellen
chown -R www-data:www-data /var/www/your-app
# Sensible Dateien schützen
chmod 644 composer.json composer.lock package.json package-lock.json
chmod 644 webpack.mix.js vite.config.js
Sicherheitsheader
// app/Http/Middleware/SecurityHeaders.php
class SecurityHeaders
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY'); // Oder SAMEORIGIN
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', implode(', ', [
'camera=()',
'microphone=()',
'geolocation=()',
'payment=()',
]));
return $response;
}
}
// In Kernel registrieren
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\SecurityHeaders::class,
],
];
Überwachung und Protokollierung
Aktivitätsprotokollierung
// composer require spatie/laravel-activitylog
// Aktivität auf Modellen protokollieren
class Post extends Model
{
use \Spatie\Activitylog\Traits\LogsActivity;
protected static $logAttributes = ['title', 'content', 'is_published'];
protected static $logOnlyDirty = true;
protected static $submitEmptyLogs = false;
public function getDescriptionForEvent(string $eventName): string
{
return "Beitrag wurde {$eventName}";
}
}
// Manuelle Protokollierung
activity()
->causedBy(Auth::user())
->performedOn($post)
->withProperties(['ip' => request()->ip(), 'user_agent' => request()->userAgent()])
->log('Beitrag wurde aktualisiert');
Fehlerüberwachung
// app/Exceptions/Handler.php
class Handler extends ExceptionHandler
{
public function register(): void
{
$this->reportable(function (Throwable $e) {
if (app()->environment('production')) {
// An Überwachungsdienst senden
// \Sentry\Laravel\Integration::report($e);
}
});
$this->renderable(function (AuthenticationException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => 'Nicht authentifiziert.'], 401);
}
return redirect()->guest(route('login'));
});
$this->renderable(function (AuthorizationException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => 'Nicht autorisiert.'], 403);
}
return redirect()->back()->with('error', 'Sie sind nicht berechtigt, diese Aktion auszuführen.');
});
}
}
Sicherheitscheckliste
Vor der Produktion
-
APP_DEBUGauffalsegesetzt -
APP_KEYgeneriert und gesetzt - HTTPS überall erzwungen (Middleware, Trusted Proxies)
-
.envnicht in der Versionskontrolle -
SESSION_SECURE_COOKIEauftruegesetzt (bei HTTPS) - Mass Assignment-Schutz (Fillable/Guarded) korrekt konfiguriert
- CSRF-Schutz aktiviert und Token in Formularen enthalten
- Sicherheitsheader konfiguriert (CSP, X-Frame-Options usw.)
- Ratenbegrenzung für sensible Endpunkte eingerichtet
- CORS ordnungsgemäß konfiguriert (kein
*in Produktion) - Dateiberechtigungen korrekt gesetzt
- Fehlerbehandlung (keine Stacktraces in Produktion)
- Passwortrichtlinien durchgesetzt (Länge, Komplexität)
- E-Mail-Verifizierung aktiviert
- Sanctum-Token-Ablauf konfiguriert
- Sitzungsablauf und -rotation konfiguriert
Regelmäßige Wartung
- Abhängigkeiten regelmäßig aktualisieren:
composer update - Auf Sicherheitslücken prüfen:
composer audit -
php artisan cache:clearnach Konfigurationsänderungen - Protokolle auf verdächtige Aktivitäten überprüfen
- Sanctum-Token regelmäßig rotieren
- API-Endpunkte auf ordnungsgemäße Autorisierung prüfen
- CSP-Header aktualisieren, wenn neue externe Ressourcen benötigt werden
- Logging- und Überwachungssysteme überprüfen