Software Teams

Security Hardening for Web Applications: A Developer Checklist

Essential security hardening practices for web applications covering authentication, input validation, headers, and dependency management.

Security Is Not a Feature, It Is a Practice

Security vulnerabilities are not just theoretical. Automated scanners probe every public-facing application constantly. If your application has a SQL injection vulnerability, it will be found and exploited. Not maybe. Will.

This checklist covers the security hardening practices that every web application should implement. It is not exhaustive, but it covers the vulnerabilities that account for the vast majority of real-world breaches.

Authentication and Session Management

Password Handling

Never store plaintext passwords. Use bcrypt or Argon2id for hashing. Laravel handles this by default, but verify your configuration:

// config/hashing.php - verify this is bcrypt or argon2id
'driver' => 'bcrypt',
'bcrypt' => [
    'rounds' => 12, // Increase from default 10 for better security
],

Enforce password complexity. Not arbitrary rules like "must contain uppercase, lowercase, number, and symbol." Instead, check against breached password databases:

// Laravel validation rule
'password' => ['required', 'string', 'min:12', Password::defaults(), 'uncompromised'],

The uncompromised rule checks passwords against the Have I Been Pwned database via a k-anonymity API. No passwords are transmitted.

Session Security

// config/session.php
'secure' => true,        // Cookies only sent over HTTPS
'http_only' => true,     // Cookies not accessible via JavaScript
'same_site' => 'lax',   // Prevents CSRF from external sites
'lifetime' => 120,       // Session expires after 2 hours of inactivity

Regenerate the session ID after authentication to prevent session fixation:

// Laravel does this automatically in the login controller
$request->session()->regenerate();

Multi-Factor Authentication

MFA is no longer optional for applications handling sensitive data. Laravel Fortify provides TOTP-based MFA out of the box. Prefer time-based one-time passwords (TOTP) over SMS codes since SMS is vulnerable to SIM swapping attacks.

Input Validation and Output Encoding

Validate Everything

Never trust client input. Validate on the server, even if you validate on the client.

class StoreInvoiceRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'customer_id' => ['required', 'integer', 'exists:customers,id'],
            'amount' => ['required', 'numeric', 'min:0.01', 'max:999999.99'],
            'description' => ['required', 'string', 'max:500'],
            'due_date' => ['required', 'date', 'after:today'],
        ];
    }
}

Whitelist, do not blacklist. Define what is allowed, not what is forbidden. Blacklists always miss something.

SQL Injection Prevention

Use parameterized queries exclusively. Laravel's Eloquent and query builder handle this by default:

// Safe: parameterized
User::where('email', $email)->first();

// Dangerous: raw input in query
DB::select("SELECT * FROM users WHERE email = '$email'");

// Safe raw query with bindings
DB::select('SELECT * FROM users WHERE email = ?', [$email]);

Be especially careful with whereRaw(), orderByRaw(), and DB::raw(). These bypass parameter binding if used carelessly.

Cross-Site Scripting (XSS) Prevention

Escape output by default. Blade's {{ }} syntax auto-escapes output. Only use {!! !!} when you explicitly need unescaped HTML, and sanitize the input first.

Content Security Policy headers provide defense in depth:

// In middleware or web server configuration
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");

CSP prevents the browser from executing inline scripts or loading scripts from unauthorized domains, even if an attacker manages to inject HTML.

HTTP Security Headers

Add these headers to every response:

# Nginx configuration
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

HSTS (Strict-Transport-Security) is critically important. It tells browsers to only communicate with your server over HTTPS, preventing protocol downgrade attacks.

CSRF Protection

Laravel includes CSRF protection by default for web routes. Verify it is not disabled:

// In VerifyCsrfToken middleware, ensure your routes are not excluded
protected $except = [
    // Only webhook URLs from trusted sources should be here
    'webhooks/stripe',
];

For API routes using token-based authentication, CSRF tokens are not needed since the authentication mechanism itself prevents cross-site request forgery.

Authorization

Authentication proves who you are. Authorization determines what you can do. Mistakes here are among the most common and dangerous vulnerabilities.

Check authorization on every request. Not just in the UI layer.

// Controller
public function show(Invoice $invoice)
{
    $this->authorize('view', $invoice);
    // ...
}

// Policy
public function view(User $user, Invoice $invoice): bool
{
    return $user->id === $invoice->customer_id
        || $user->hasRole('admin');
}

Insecure Direct Object References (IDOR). The URL /invoices/42 should not let user A view user B's invoice. Always verify ownership or permissions, even for read operations.

Dependency Management

Keep dependencies updated. Known vulnerabilities in outdated packages are the easiest attack vector. Run composer audit and npm audit in your CI pipeline.

Review new dependencies before installing. Check the package's maintenance status, download count, and recent commit history. Abandoned packages with known vulnerabilities are common in the PHP and JavaScript ecosystems.

Lock your dependency versions. Always commit composer.lock and package-lock.json. This ensures reproducible builds and prevents supply chain attacks from compromised package updates.

Secrets Management

  • Never commit secrets to version control. Use environment variables or a secrets manager.
  • Rotate API keys and database passwords periodically.
  • Use separate credentials for each environment (development, staging, production).
  • Audit who has access to production secrets.

Logging and Monitoring

Log security-relevant events: failed login attempts, permission denials, input validation failures, and unusual access patterns. Monitor these logs with alerting rules that notify your team of potential attacks in real time.

A security breach you detect in minutes is manageable. One you discover weeks later is a catastrophe.

Let's talk about your software teams needs

Whether you're modernizing your infrastructure, navigating compliance, or building new software - we can help.

Book a 30-min Call