Articles

Enter a search term to find articles.
November 21, 2025

Protect Your Admin Panel with Cloudflare Zero Trust — For Free!

*A quick security win — no coding, no plugins, no cost.* 🎯 **Why protect your Admin Panel?** Admin URLs are like gold mines for attackers. Even if your login is secure, exposing these URLs publicly allows: - Bot scanning - Brute-force attempts - Vulnerability probing The best protection? Make them invisible to the internet. ## 🚀 Solution: Hide Admin Panel behind Cloudflare Zero Trust 🛠️ **What we'll do?** We'll block public access to `/admin` (and related URLs), and allow access only to authorized users — protected by email, Google Login, OTP or identity provider. ### Step-by-Step Setup 1️⃣ **Go to Cloudflare -> Zero Trust** - Login to Cloudflare. Then on the sidebar click **Zero Trust** - Choose the free plan, but Cloudflare will still ask you to provide payment details - If you're working with a team, I recommend setting up using an email address that everyone has access to so they can easily get the one-time PIN 2️⃣ **Manage Identity Providers** - On Zero Trust Dashboard, on the sidebar click **Integrations** -> **Identity providers** - These will provide a way for you to authenticate for your login panel. **One-time PIN** is enabled by default — this will send an OTP to the registered email address. - Of course, you can also add other ways to authenticate: ![Zero Trust Login Methods](https://marvinquezon.com/storage/uploads/screenshot-2025-11-21-at-113647-am.png) 3️⃣ **Create a Policy** - On the sidebar click **Access controls** -> **Policies** - On Policy Name: Allow Team Members (or whatever policy name you want that makes sense) - Duration: Set to default: 24 hours - Then on **Add Rules** -> **Selector** choose **Emails** - here you can add the emails of your team members who will need access to your admin panel. - Once done, scroll down and click **Save** 4️⃣ **Add your Application** - On the sidebar click **Access controls** -> **Applications** - Click **Add Application** then select **Self-hosted** - Application Name: **My Website Admin** (or whatever makes sense to you) - Click on **Add public hostname** -> Input your domain and path to admin like so ``` Domain: https://mydomain.com Path: /admin* // This will secure the admin and all related URLs ``` - Then on Access Policies -> click **Select existing policies** then apply the policy that you've created - Then scroll down to **Login Methods** and make sure **Accept all available identity providers** is turned on so that any providers you've setup on the 2nd step will be used. - Then **Save** 5️⃣ **Test** - Visit your admin panel - Instead of admin login, you'll see Cloudflare Access prompt - Confirm email -> enter code that was sent to the registered email -> Cloudflare grants access - After that, you'll see your normal admin login page — but only after identity check ⭐️ **Why This Works (Even on Free Plan)** ✔️ Access rules (email-based) ✔️ One-time PIN ✔️ Google login ✔️ Protect multiple paths ✔️ Works with Laravel, WordPress, Node, etc. ## 🔚 Final Thoughts This is the fastest way to secure your admin panel — without changing any code, installing any package, or paying for a plan. *Makes your panel invisible, protected, and accessible only to you — for free.*

November 16, 2025

How to Add Cloudflare Turnstile to Laravel in 5 Minutes (No Packages Required)

If you’ve built a public-facing web app, you eventually run into the same annoying problem: bots. They fill out your forms, spam your inbox, and waste your server’s time. Most developers immediately think of Google reCAPTCHA, but there’s a cleaner option that solves the same problem without the tracking baggage: **Cloudflare Turnstile**. Turnstile is Cloudflare’s privacy-friendly CAPTCHA alternative. * ❌ No user friction. * 🚍 No weird “click the traffic lights” puzzles. (ehem.. Google) * ⏲️ And setup takes only a few minutes. In this quick guide, I’ll show you why you should use Turnstile — and how to integrate it into a Laravel app without any third-party packages. ## Why use Cloudflare Turnstile? Here are the biggest reasons I migrated to it: **1. No user frustration** Turnstile doesn’t make users solve puzzles. It verifies silently in the background unless something looks suspicious. **2. Privacy-respecting** Unlike reCAPTCHA, Turnstile doesn’t track users across the web. No profiling, no annoying consent banners. **3. Dead simple to implement** It’s literally: * Load a script * Place a widget * Verify the token server-side Done! **4. Completely free** Cloudflare offers Turnstile with unlimited usage on the free plan. ### Step 1 — Create a Turnstile Widget in Cloudflare 1. Log in to Cloudflare 2. Application Security → Turnstile 3. Click **Add Widget** 4. Set: * Name: e.g: my-domain-name-turnstile * Add Hostnames: If you already have existing hostnames setup in Cloudflare you can select it, or you can just type your domain name manually. You can also add your local domains here - very useful if you are using Herd on your local machine. * Widget Mode: Managed (recommended) * Would you like to opt for pre-clearance for this site?: Just select "No" 5. Copy your **Site Key** and **Secret Key** ### Step 2 — Add the Keys, Widget and Script into Laravel Add these to your `.env` file ``` # Cloudflare Turnstile Captcha TURNSTILE_ENABLED=true # This will be so that you can just disable it quickly, especially on your tests TURNSTILE_URL=https://challenges.cloudflare.com/turnstile/v0/siteverify TURNSTILE_SITE_KEY=Your-Turnstile-Site-Key TURNSTILE_SECRET_KEY=Your-Turnstile-Secret-Key ``` Then, configure these values on your `config/services.php` file ``` // On services.php 'turnstile' => [ 'enabled' => env('TURNSTILE_ENABLED', false), 'turnstile_url' => env('TURNSTILE_URL'), 'site_key' => env('TURNSTILE_SITE_KEY'), 'secret_key' => env('TURNSTILE_SECRET_KEY'), ], ``` Then add the Widget into your Blade template — this usually goes into public facing forms such as registration, forgot password, contact us, etc. ``` ``` Then add their script outside the body of your base template ``` ``` ### Step 3 — Create a Rule class and use it on your form validations This sample rule class below provides a way to ignore `required` validation on the Turnstile field if the `config('services.turnstile.enabled')` is set to `false`. ```

June 1, 2023

Please, please, please - use Eloquent attribute Casting

In my years of developing systems and API's for various business verticals, one of the most prominent problem I have encountered and still encountering until now are attributes that were not properly cast to their expected data type. It is always such a headache when you know you should be working with a `boolean` value but receiving a `string` instead. Or an `integer` or `float` value that is not properly mutated and left as `string`, especially when working on financial-related stuff. Or worse, an `array` of objects that is, guess what, left as `string`. In Laravel, attribute casting has vastly improved since version 7.x. We only used to have [Mutators and Accessors](https://laravel.com/docs/6.x/eloquent-mutators#array-and-json-casting) available to us but now, [class-based Custom Casts](https://laravel.com/docs/10.x/eloquent-mutators#custom-casts/) is available. And it is also rich with other sub-features such as [Value Object Casting](https://laravel.com/docs/10.x/eloquent-mutators#value-object-casting) and [Serialization](https://laravel.com/docs/10.x/eloquent-mutators#array-json-serialization). Just another reason to already bunch of reasons why I love working with Eloquent ORM. Casting is our first layer of assurance that we are getting correct data types post database query. And these framework-native features give us more than enough reason to always casts our attributes. So please, don't be lazy and utilize it, for your own sanity. **TIP:** If you don't use Eloquent when querying data from your database but still want your attributes to be cast properly? Just manually instantiate your rows instead: ``` use App\Models\Posts; $posts = \DB::table('posts')->get(); $posts = $posts->map(fn ($post) => new Post($post)); ```

July 16, 2022

Level 0: Implementing PHP Static Analysis on an 8-year-old codebase

I recently got a new opportunity to work with a company as a developer. One of my first tasks is to implement [PHP Static Analysis](https://phpstan.org) on an 8 year old-ish codebase. The task seemed daunting at first but I had 2 things going for me; first, they were using Laravel framework so we can easily pull up [Larastan](https://github.com/nunomaduro/larastan), and second, the codebase has good amount of tests which gave me a huge boost in confidence. In this blog, I aim to give you few tips when implementing static analysis on a legacy codebase, and explain to you some of the static analysis errors that I encountered and how I solved it. ## First Time If this is your first time implementing static analysis, I would suggest reading through the [PHPStan Documentation](https://phpstan.org/user-guide/getting-started) first before even installing it on your codebase. Their documentation is quite comprehensive so it should not take you very long. I would also suggest, though not necessary, to watch [Nuno Maduro's talk about Types in PHP](https://youtu.be/jObcE58UCB8) for better understanding of PHP types and Larastan. By now you should know that there are **rule levels** in PHPStan - from Level 0 to Level 9. Levels determine the strictness of the rules where 0 is the loosest and 9 is the strictest. If you're implementing it on a huge codebase you should **always start at Level 0** or you will get overwhelmed with too many errors to fix. ## Installation Installing [Larastan](https://github.com/nunomaduro/larastan) should be quick and easy, just follow through the documentation on the readme file in the repository. By now, you should have a configuration file within the root of your project directory. Mine is stored as `phpstan.neon` and looks more or less like this: ``` includes: - ./vendor/nunomaduro/larastan/extension.neon parameters: checkMissingIterableValueType: false noUnnecessaryCollectionCall: false reportUnmatchedIgnoredErrors: false # Paths to scan and analyse. paths: - app # The level: 9 is the highest level. level: 0 # Circle CI configuration. parallel: jobSize: 20 maximumNumberOfProcesses: 8 # List of errors to be ignored. ignoreErrors: - '#PHPDoc tag @var#' - '#Unsafe usage of new static#' # List of paths that are excluded. excludePaths: - tests/ ``` Once you've created the configuration file, just run `./vendor/bin/phpstan analyse`, wait for couple of seconds (might take longer depending on the size of your project) and it should show you the errors on your terminal. Let's go through some of the errors that I have encountered. ## Error Patterns ### Access to an undefined property App\Foobar::$baz. This error is self-explanatory. Within the class Foobar you are doing a `$this->baz` property call. However the property is not actually declared in the class. Although PHP allows this through dynamic property, **static analysis is protecting you from making unexpected property calls to an object**. ***Fix: Just declare the property on the class or remove it entirely.*** ### Method App\Foobar::handle() should return int but return statement is missing. Another self-explanatory error. The method `handle()` within class Foobar is expected to return something but there is no return statement. This is usually because there is a doc block above the method declaration. ```php /* * @return int */ public function handle() { // Some code without return statement... } ``` ***Fix: Add a return statement or remove the doc block.*** ### Relation 'user' is not found in App\Models\Post model. This error was a bit tricky at first, because it would still appear even though the relation `user` has been declared on the `App\Models\Post` model class. ***Fix: Add a return type on the relation method.*** ```php // In App\Models\Post class... use Illuminate\Database\Eloquent\Relations\BelongsTo; public function user() : BelongsTo // Add this.. { return $this->belongsTo(User::class); } ``` ### Deprecated in PHP 8.0: Required parameter $foo follows optional parameter $bar. Another self-explanatory error and is obviously an issue only on PHP 8.0 and up. Basically, what's happening is that there is a method within your class that looks something like this. ```php public function something($bar = null, int $foo) { // ... } ``` As you can see, `$bar` is an optional parameter while `$foo` is not. Required parameters should be at the left of optional parameters. ***Fix: Refactor the method and its usages to make sure required parameters are on the left.*** ### app/Console/Commands/Foo.php: Result of method Illuminate\Console\Command::error() (void) is used. This is because in the `handle` method of the `Foo.php` class, we have this call ```php public function handle() { // Some codes... return $this->error('...'); } ``` The issue is that the result of `$this->error()` call is void type and therefore should not be used as a return statement. ***Fix: Update return statement with the correct integer code.*** And that's pretty much all of the error patterns I have encountered implementing level 0 static analysis. On the first run, there were 380 errors found with these patterns on different parts of the codebase. Hopefully you are starting to realize the benefits and protection static analysis can give you at the very beginning. I will continue this article once I get to work on the next levels. Cheers!

May 14, 2022

Refactoring Techniques: Lookup Tables

One of the easiest and most common techniques you can reach out for when refactoring your code is a **Lookup Table**. A lookup table is basically just a table, can be objects, arrays, database tables, redis cache or whatever, that you can look in to find values. Let's take a look at some common refactoring examples. # Refactoring multiple 'OR's For our first example, let's say that we have a `User` model that has a `type` property. ``` // Imagine you have something like this... if ($user->type === 'administrator' || $user->type === 'teacher' || $user->type === 'guardian') { // Do something here... } ``` You can imagine if in the future we need to add more user type that the condition could get long. This is one perfect candidate where we can use lookup tables. Take a look... ``` // Put our values in a lookup table, then proceed with our condition... $table = ['administrator', 'teacher', 'guardian']; if (in_array($user->type, $table)) { // Do something here... } ``` We've refactored to a lookup table, in this case an array, to group our possible values. And then used the lookup table in our condition to check against the user type. With this refactor, should we need to add more user types, all we have to change is our lookup table. This is what we call **isolating the change**. And with Laravel, we can refactor this further to be much more readable, like so... ``` $table = ['administrator', 'teacher', 'guardian']; if(collect($table)->contains($user->type)) { // Do something here... } ``` Using the collection the condition becomes much more clearer that we are checking if the table contains the given user type. 👌 # Imperative vs Declarative Programming Using the same example above, imagine a scenario that when you login a user, you need to redirect them to their dedicated homepage based on their user type. You would probably have something like this... ``` public function redirectUser($user) { if ($user->type === 'administrator') { return redirect('/administrator/dashboard'); } elseif ($user->type === 'teacher') { return redirect('/teachers/schedules'); } elseif ($user->type === 'guardian') { return redirect('/guardians/learners'); } } ``` As you can see, this approach is kind of describing the step by step on how to determine where to redirect the user based on their type. When you are specifying the exact steps to get the results, this is what we call **imperative programming**. Now maybe your first instinct is to refactor using switch statements. While that would definitely work, I would say switch statements would still somehow describe the step by step of the process. Using a lookup table would be much more cleaner. Let's take a look... ``` public function redirectUser($user) { $homepages = [ 'administrator' => '/administrator/dashboard', 'teacher' => '/teachers/schedules', 'guardian' => '/guardians/learners', ]; return redirect($homepages[$user->type]); } ``` As you can see with this approach, its much more results-focused. It is able to determine the outcome without having to describe the process step by step. This is what we call **declarative programming**. 😎 Generally speaking, it's easier for human brain to describe step by step approach instead of results-based approach especially when writing alorithms. This is why declarative programming is often a result of a refactor. # As Strategy Pattern This is almost the same as the last example, but you can use lookup tables to determine strategy that you can use for certain logic. To illustrate, using the same user type example above, imagine if we have different strategies for processing payments based on the user type... ``` public function processPayment($user) { $handler; if ($user->type === 'administrator') { $handler = new BankPaymentHandler; } elseif ($user->type === 'teacher') { $handler = new GcashPaymentHandler; } elseif ($user->type === 'guardian') { $handler = new PaymayaPaymentHandler; } // Assuming the handler classes are abiding by a contract with // a process method that accepts an instance of the user... return $handler->process($user); } ``` Again, we can refactor to a more declarative programming approach using a lookup table, like so... ``` public function processPayment($user) { $strategies = [ 'administrator' => BankPaymentHandler::class, 'teacher' => GcashPaymentHandler::class, 'guardian' => PaymayaPaymentHandler::class, ]; return (new $strategies[$user->type])->process($user); } ``` Hope you've learned how to use lookup tables to refactor your code. Happy coding!

May 6, 2022

Tips for developing with VueJS, InertiaJS, Laravel and TailwindCSS (VILT) Stack

VueJS, InertiaJS, Laravel, TailwindCSS - more commonly known as **VILT** stack is one of the modern approaches for developing web apps with monolithic architecture. In this post, I want to share with you some tips based on my personal experience developing with VILT stack. # Inertia Version When you install InertiaJS for Laravel, the process will need you to publish the middleware `HandleInertiaRequests` within your web middleware group. In this class is a public method `version` that is used by Inertia to determine the current version of your assets. However, this particular method has some issues when you are running tests that are doing json assertions. What I recommend is to disable this when running tests, like so... ``` // app/Http/Middleware/HandleInertiaRequests.php public function version(Request $request): ?string { return (! app()->runningUnitTests()) ? parent::version($request) : null; } ``` # The Inertia Testing Header [InertiaJS comes with its own tools and approach for writing tests and performing assertions.](https://inertiajs.com/testing) However, for endpoint tests, if you still want to use the traditional response assertions from Laravel, you can pass `['X-Inertia' => 'true']` as a header for each request like so... ``` public function can_get_user_edit_page() { $user = User::factory()->create(); $response = $this->get( route('admin.user.edit', $user), ['X-Inertia' => 'true'] // Set this... ); $response->assertJsonFragment(['component' => 'Admin/User/Edit']) ->assertJsonFragment(['name' => $user->name]); } ``` # When Login is not an Inertia page When developing an app that has a login page that is not part of Inertia pages, you will encounter this particular gotcha. When the authentication of your user expires, you will see your login page displayed on the modal like this: ![login-page-on-modal](https://marvinquezon.com/storage/uploads/login-page-on-modal.png) This would normally occur if the user left the app open and idle for quite sometime and their session expired, then they tried to navigate on an Inertia page. Manually refreshing the page would do the trick. But if the user tries to login through the form that is on the modal and is successful, it would then render the page on the modal. Pretty nasty UI issue. You can easily replicate this by logging in a user, delete the session then navigate to an Inertia page. Luckily, this can easily be solved by just overriding the `unauthenticated` method in your `App\Exceptions\Handler` class, and making it so that Inertia handles the redirection to the login page for HTTP response like so... ``` // app/Exceptions/Handler.php use Inertia\Inertia; class Handler extends ExceptionHandler { //... protected function unauthenticated($request, AuthenticationException $exception) { // Check first if request is an inertia request. // And if so, we redirect to the login page... if ($request->hasHeader('X-Inertia', true)) { return Inertia::location(route('login')); } if ($this->shouldReturnJson($request, $exception)) { return response()->json(['message' => $exception->getMessage()], 401) } return redirect()->guest($exception->redirectTo() ?? route('login') } } ``` # Let TailwindCSS scan your PHP files InertiaJS was made for building modern monolithic apps. This means coupling your frontend with your backend much like when building using Blade views. So there are times that some frontend entities like classes can come from the backend. For one, I like using Enums for determining status of certain models. The Enums would also hold some color values that frontend can render. With that in mind, if you're using TailwindCSS as your frontend CSS framework, you might want to allow it to scan your PHP files so it can detect classes that are stored at the backend. You can do this by modifying your `tailwind.config.js` file and adding the app path within the content array, like so... ``` module.exports = { content: [ './resources/**/*.js', './resources/**/*.vue', './app/**/*.php', ], } ``` # Inertia View Composer Package This one would sound like a shameless plug. But one really powerful feature that InertiaJS has not yet implemented as of this writing is the ability to be able to share data to the frontend based on the page name. Laravel Blade views has this and its called [**View Composers**](https://laravel.com/docs/9.x/views#view-composers). So you might want to check out this package that I created called [Kinetic](https://github.com/ambengers/kinetic) that does exactly that - share data to your frontend based on the Inertia page name. I've personally used this in one of the latest projects that I am working on and it helped me alot. Happy coding!

April 30, 2022

Practical Tips for Writing Beautiful PHP Code

Here are some straight-forward and practical tips on how to make your PHP code beautiful. # Give it some space As romantically-cliche as it may sound, sometimes all your code needs is a bit of space. A little bit of breathing room so you and your team can easily read it better. And as basic as it may seem, many, both new and experienced developers tend to still forget the idea of adding spaces on their code. Take a look at these code blocks below... ``` public function test_can_upload_avatar() { $this->user = User::factory()->create(); $avatar = UploadedFile::fake()->image($filename = 'avatar.jpeg'); $this->post(route('user.profile.avatar'), ['avatar' => $avatar]); $this->assertDatabaseHas('media', [ 'model_type' => $this->user->getMorphClass(), 'model_id' => $this->user->id, 'file_name' => $avatar->hashName(), ]); $this->assertNotNull($this->user->avatar->first()); $this->assertInstanceOf(Media::class, $this->user->avatar->first()); } ``` ``` public function test_can_upload_avatar() { $this->user = User::factory()->create(); $avatar = UploadedFile::fake()->image($filename = 'avatar.jpeg'); $this->post(route('user.profile.avatar'), ['avatar' => $avatar]); $this->assertDatabaseHas('media', [ 'model_type' => $this->user->getMorphClass(), 'model_id' => $this->user->id, 'file_name' => $avatar->hashName(), ]); $this->assertNotNull($this->user->avatar->first()); $this->assertInstanceOf(Media::class, $this->user->avatar->first()); } ``` They are obviously the same piece of code. I don't know about you, but just eye-balling these code blocks, I definitely find the latter much easier to take in and understand because of its ample spacing. Like, I don't feel the need to bring my face closer to the screen of my laptop just to be able to understand what each line of code does, if you know what I mean. As a rule of thumb, try to put each statement (anything that ends with a semi-colon) on its own line unless 2 or more lines are doing the same type of action (i.e. the assertions on the example above, assigning variables, etc.). # Use PHP CS Fixer One advantage PHP has over other programming languages is that we have our own set of coding standards that developers generally agree upon. The [PHP Standards Recommendation or PSR](https://www.php-fig.org/psr/) are set of recommendations for styling our code. From **Basic Coding Standard**, **Coding Style** and **Autoloading Standards** the PSR has you covered so you don't have to invent your own. Another good thing is that you can automate the implementation of these standards on your IDE using the [PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) tool. Modern IDEs like Sublime Text, PhpStorm and VS Code have their own packages that you can just download from their respective package managers to set this up. All you need to do is to configure the set of rules that may want to implement for you or your team. If you have no idea on what rules to implement, here's a link to a [gist of set of rules that I personally use in my projects](https://gist.github.com/ambengers/c112d75bb7e14b41daf99dc2abad7690). I have mine set to auto run whenever I save the PHP file so that I am sure that these standards will always apply no matter what. You can also refer to this [PHP-CS-Fixer Cheat Sheet](https://mlocati.github.io/php-cs-fixer-configurator/#version:3.8) if you want to check on the meaning and effect of each rule. # Leave Meaningful Comments Another very basic yet many-tend-to-forget way of beautifying their code and helping themselves and/or their teammates understand their code better is by using comments. Not just comments, meaningful comments. Comments that actually makes sense when we you read it. I personally believe that writing meaningful comments actually reflects how much care a developer have put into their code. This is actually one reason why Laravel framework is so popular. And like-minded developers tend to gravitate towards it, developers who actually care for their code. Personally, as a rule of thumb, I leave comments on areas that I feel like, **if I don't remember the full context of how the code works, it will be quite hard for me to understand it again if I read it 6 months from the time that I wrote it**. I also try to follow Laravel's way of writing comments - where each line is a couple of characters shorter the the one above it.

Marvin Quezon · Copyright © 2025 · Privacy · Sitemap