Lock with clouds in background
Jim Hlad profile photo Jim Hlad August 1, 2025 7 min read

12 Simple Tips for Securing Your Web Apps

TLDR;

In this post I share some simple tips you can use to improve the security of your web application. Although many of these are best practices which apply to cloud-hosted web apps in general, I'll also share a few Laravel-specific examples to illustrate some key points. It's important to remember that security isn't black and white. Security exists on a spectrum and there is no such thing as being "totally secure" against every type of threat. The hypothetical example I have in mind for this post is a small, public-facing Laravel web application (e.g. perhaps a simple e-commerce website) with a few front-end forms and a back-end admin portal area which allows staff to add some site content. The list here is not exhaustive and is meant purely for informational purposes.

1. Add Multi-factor Authentication (MFA) Everywhere

This tip earns the top spot because MFA is usually relatively simple to set up, and it’s such a powerful security measure when used correctly. On the flip side, failing to set up MFA opens the door to many potential problems like stolen passwords or brute-force attacks.

When MFA is setup correctly, an attacker would essentially need two or more independent pieces of information to prove that they are you (hence the "multi" in multi-factor). First they need your actual account password (which should always be a strong password), but with MFA enabled they'll also need either a temporary code (e.g. from an authenticator app on your phone) or physical access to your hardware security key (like a YubiKey).

If you're managing a team or organization, it’s not enough to just recommend MFA. Whenever possible, you should enforce it via policy. Services like GitHub allow you to set this up at the organization level, meaning that members will need to configure MFA in order to start contributing alongside your existing team.

2. Use SSH Key-Based Authentication (NOT Passwords) for Your Server

If you've decided to spin up a new server for your app, such as an EC2 instance on AWS or a Droplet on Digital Ocean, one of the easiest ways to secure it is to avoid password-based logins and instead use SSH key-based authentication. Since passwords can be stolen, guessed or brute-forced, SSH based authentication offers a big advantage by requiring a unique private key to access the server. Some automated hacking tools may even skip the server and move on if it finds that password authentication is disabled.

If you’re using Laravel Forge to manage your servers, the good news is that SSH key management is built right into the UI. Forge disables password logins by default on newly provisioned servers, meaning you’re already a step ahead just by using it.

3. Configure a Firewall to Filter Unwanted Traffic

A properly configured firewall is one of the simplest ways to shrink your attack surface. The type of firewall you use will depend on the architecture of your application, your security needs, and the capabilities of the hosting provider that you're using. For example, let's say your hosting a Laravel web application using a serverless architecture on AWS. You will likely be using services like API Gateway, Lambda for compute, S3 for file storage, RDS for a managed database engine, CloudFront for your CDN, and so on.

AWS Serverless Setup with WAF
AWS Serverless Setup with WAF

In this type of setup, you can configure a Web Application Firewall (WAF) in front of your API Gateway, and setup filtering rules to enhance the security of your application. If you're using Laravel Vapor to manage your hosting environment, this can be as simple as adding a few configurations to your vapor.yml file for common tasks like rate limiting or blocking bots. For more custom use cases, you can also sign in to the AWS console to manage the WAF directly. This allows you to define advanced filtering rules, such as:

  • Blocking or rate limiting requests to sensitive URL paths like /admin
  • Match and block patterns that are consistent with SQL injection or cross-site scripting (XSS)
  • One-click deployment of pre-defined rule groups such as OWASP Top 10 protections, common threats for PHP apps, etc.

Pro Tip If you're using Laravel Vapor to manage your hosting environment, you may need to remove the firewall configuration from your vapor.yml in order to manually add custom WAF rules within AWS. I've encountered issues in the past where re-deployments through Vapor would re-create the firewall defined in the vapor.yml file and overwrite any changes which were manually applied via the AWS web console. Not sure if this is still an issue, but it's worth double checking to ensure that any manually created rules don't get dropped after each deployment.

Alternatively if you're using a simpler server-based setup with a different hosting provider, say a Digital Ocean Droplet, there's also Uncomplicated Firewall (UFW) which is a Linux-based firewall tool. If you're using Laravel Forge to manage your hosting environment then you can configure it entirely through Forge's dashboard.

4. Enforce HTTPS for All Pages

This one is a simple no-brainer. It's 2025 and there's no good reason why every page of your app shouldn't be served via HTTPS. By using HTTPS with a valid SSL/TLS certificate, you ensure that data is encrypted in transit between the browser and your server. This offers a layer of protection against man-in-the-middle (MITM) attacks, as well as better data privacy for your users who may be using your app from a less secure location (e.g. a public WiFi network at their favourite coffee shop).

5. Schedule Frequent Backups and Enable Data Encryption at Rest

In addition to encrypting data in transit, it’s also important to protect against data loss — whether accidental or malicious. Popular managed services like AWS RDS and DigitalOcean Managed Databases support automated daily backups, but you should double-check that backups are actually enabled, the retention window is appropriate (e.g., 7–30 days), and that encryption at rest is turned on. For AWS RDS, encryption must be enabled at the time of database creation, using AWS KMS-managed keys. For object storage like S3, enable bucket versioning to recover from accidental deletions or overwrites, and use SSE-S3 or SSE-KMS for server-side encryption to ensure files are stored securely. These are low-effort but important changes which can help contribute to your security and disaster recovery strategy.

Pro Tip You may think backups are just for the code and data, but many people often overlook an equally important consideration: a backup payment method! It's always a good idea to have some redundancy, and financial redundancy is no different. The last thing you want is for your account to be suspended and your data permanently deleted by your own hosting provider.

6. Use CSRF tokens for All Forms

Cross-Site Request Forgery (CSRF) attacks trick users into submitting unwanted actions on your app, often without their knowledge. The most effective way to defend against this is by requiring CSRF tokens for all state-changing requests (like form submissions). These tokens ensure that the request originated from your application and not the bad guys.

Many web framework provide tools that make this protection very easy to implement. In Laravel for example, you can use the @csrf blade directive directly inside any HTML form:

BLADE
<form method="POST" action="/posts">
    @csrf
    <!-- form fields -->
</form>

This injects a secure, hidden input field with a unique CSRF token. On the back-end, Laravel automatically validates the token using middleware included on the default web route group. If the token isn't valid, the user will get an error saying that the page has expired.

Invalid CSRF Token
Invalid CSRF Token

If for some reason you wanted to perform additional checks, you can also append your own custom middleware to the global stack as needed:

PHP
// bootstrap/app.php

use App\Http\Middleware\EnsureTokenIsValid;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        // ...
    )
    ->withMiddleware(function (Middleware $middleware): void {
        // Your own middleware here
    }))->create();

Note that if you're using an SPA framework like Inertia.js, the process may be slightly different on the front-end. However the general idea and approach remains the same.

7. Apply Strict Form Field Validation

Validating user input is one of the most important lines of defense for your application, but it’s also one of the easiest to get wrong. If your validation rules are too loose, you leave the door open to improperly formatted or malicious data. If they’re too strict, you risk frustrating legitimate users with false positives. The key is to find the right balance: be strict enough to block anything harmful or unwanted, but flexible enough to accommodate the wide variety of real world data.

Here's an example of what that balance might look like for a public form which collects first name, last name, email and postal code from users in Canada (where English and French names are the most commonplace).

PHP
// app/Http/Requests/ExampleFormRequest.php

use Illuminate\Foundation\Http\FormRequest;

class ExampleFormRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'first_name'   => ['required', 'string', 'max:50', new NameField()],
            'last_name'    => ['required', 'string', 'max:50', new NameField()],
            'email'        => ['required', 'email:rfc,strict', 'max:255'],
            'postal_code'  => ['required', 'regex:/^[A-Z]\d[A-Z] \d[A-Z]\d$/i'],
        ];
    }
}
PHP
// app/Rules/NameField.php

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class NameField implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if(! preg_match('/^[\'’0-9a-zÀ-Ÿàáâçéèêëîïôûùüÿñæœ .,-]*$/i', $value)) {
            $fail("There was an error validating the provided name information");
        }
    }
}

Of course there will always be edge cases, but rules like these limit the types of techniques a bad actor could use to try to attack your forms. Striking the right balance for your app is often a matter of judgement, testing and refinement. For public forms that are often the target of spammers, integrating a captcha or honeypot for spam prevention is another measure worth considering.

Pro Tip Adding input masks on the front-end is a great way to set expectations with your users, and minimize the chances that they will receive an annoying server-side validation message upon submission.

8. Escape User-Generated Content (UGC)

If your app allows user-generated content, such as allowing users to post comments for a blog or adding reviews for products you sell, then it's important to properly escape this content. This helps protect your app from bad actors who may try to enter malicious coding instructions into your forms, with the goal of having this code included on the front-end of your website, which then gets executed by your unsuspecting user's browser (e.g. XSS attacks). Luckily if you're using Laravel, blade's {{ }} echo statements are automatically sent through PHP's htmlspecialchars function to prevent XSS attacks.

In some special situations you may want to allow unescaped data to appear on the front-end (e.g. displaying content with HTML formatting). Laravel provides an alternate syntax to accomplish this: {!! $name !!}, however this should only be used after careful consideration.

Pro Tip Be very careful when echoing content that is supplied by users of your application. In most cases the escaped, double curly brace syntax should be used.

9. Mind Your Dependencies

Most modern web apps nowadays rely heavily on third-party packages. These open source dependencies save time and allow you to build features without constantly re-inventing the wheel. However with this added convenience comes the need to make sure all these packages are kept up to date. A vulnerability in just one package can become an entry point into your entire application.

If you're using GitHub for your version control system, one thing that can help is enabling GitHub Dependabot, which can automatically scan your project and help notify you when a dependency has a known vulnerability. It even classifies them according to severity (e.g. Critical, High, Medium, Low) so that you can prioritize addressing the most urgent items first. However since you're usually going to be dependent on the package's authors for implementing the actual fix, it's important to choose dependencies carefully when building out your application. Choose packages that have widespread adoption and where you trust the authors to keep things up to date long-term. Abandoned packages and compatibility issues can compound over time and eventually prevent you from updating core parts of your application (like Laravel) if you're not careful.

Pro Tip Although less common, packages can sometimes change their licensing terms between versions. For example, switching from a very permissive license like MIT in version 5.x to some more commercial model in version 6.x. Blindly updating could introduce legal complications. It's good practice to keep tabs and maintain a general awareness of the dependencies your app has.

10. Use Branch Protection Rules

For those managing a team with more than one developer, branch protection rules are another great way to improve the security of your application. The idea here is to require a code review prior to allowing changes to be merged into sensitive branches, especially the main branch. Having multiple eyes on features and bug fixes increases the chances of proactively catching security issues before they're even pushed into production. It's also good practice in general since it promotes a culture of knowledge sharing and an awareness of what your fellow teammates are working on.

Pro Tip For more granular control over the review process, GitHub has a special .CODEOWNERS file for specifying which team members should be flagged to review particular sections of the codebase. You can even set different reviewers for specific files like composer.json or package.json (e.g. to help get more eyes on newly added packages) or for pulling in different people based on subject matter expertise.

11. Minimize Your Attack Surface

The less you expose, the less there is to exploit. While it’s true that “security by obscurity” isn’t a complete strategy by itself, minimizing your app’s footprint is still a smart move when used alongside other security measures. For example, if your Laravel web app has an admin portal, you might consider choosing a less common URL path than /admin for the back-end. Instead choosing something like /dashboard-portal can help reduce the discoverability of the private areas of your app.

Some frameworks like Laravel also include out-of-the-box 404 error page templates. These are shown when the user tries to access a page that does not actually exist. There's nothing wrong with using these pages, but if you're really concerned about giving a potential bad actor too much information to work with, you could consider rolling out your own custom 404 page (and for any other returned error codes as well). It's always a good idea to view the source code of your web app in the browser so that you have a better awareness of the information that you're making publicly available to all visitors.

Another often overlooked aspect of minimizing your app's footprint is the data returned by AJAX requests. Avoid returning entire model objects or unnecessary metadata in your API responses. Only include what is needed for the current page to function. Exposing too much information can inadvertently give attackers insight into your database structure, internal IDs, or other information they don't need to know.

Pro Tip If you're using Laravel, you can use Resource classes to control what information is sent to the browser when an API call is made. Rather than simply taking the array form of the model and subtracting data, it's usually best to be explicit and list each field or relationship individually.

12. Follow the Principle of Least Privilege

This principle has been a common thread throughout many of the tips above. Whether it’s limiting open ports, restricting access to admin routes, or tightening permissions on users and services, the principle of least privilege is more of a philosophy than a tangible security measure. It means giving people and systems only the access they need to perform a particular task, and nothing more. Embracing this mindset can help minimize the scope of any damage in the unfortunate event of a security breach.

If your app handles mission critical data, sensitive PII, or other types of information that requires stricter compliance with regulations, then you may also want to get a proper cybersecurity assessment done to ensure that your app is ready for prime time.

Up Next

How to Add Dark Mode to Your Laravel Website (SSR) Using Tailwind 4.x . ' Featured Image'

How to Add Dark Mode to Your Laravel Website (SSR) Using Tailwind 4.x

In this post we'll see how simple it is to add a dark mode toggle to your Laravel website using Tailwind 4.x. Although there are a few different ways to accomplish this, this post focuses on...

August 13, 2025 • 3 min read
Adding IP Whitelisting to Your Filament PHP Admin Panel . ' Featured Image'

Adding IP Whitelisting to Your Filament PHP Admin Panel

Want to restrict access to your Filament admin panel to only specific IPs? Here's the coles notes.

July 30, 2025 • 5 min read