timgavin's avatar

Validator fails and redirects if field is empty (production only)

Got a weird one here and nothing I've come across solves it, and it seems pretty simple.

I have a form with one field: amount. In development, if I submit the form with an empty field I'll see the validation errors. Everything works as it should.

However, in production, if I submit an empty form I get redirected to the /dashboard view (which is also where a user gets redirected after login). BUT, if I fill out the field then validation succeeds and I move to the next step.

Again, everything works normally in development, but validation gets redirected with an empty field in production. I'm using Forge for production, and PHP OpCache is disabled.

I've never seen this before; what the heck is going on?

<?php
namespace App\Http\Controllers\Stripe;

use App\Actions\Stripe\PayoutUserAction;
use App\Models\Stripe\StripePayout;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

public function store(Request $request, PayoutUserAction $payoutUserAction)
{
    // this shows if the form is empty or filled
    dd('here');

    $request->validate([
        'amount' => 'required|numeric',
    ]);

    // this shows ONLY if the form is filled
    dd('here 2');

    ....
}
0 likes
9 replies
LaryAI's avatar
Level 58

The issue seems to be related to the validation process in the production environment. One possible solution is to check if the request is an AJAX request before performing the validation. If it is an AJAX request, return the validation errors as a JSON response. Otherwise, redirect back with the validation errors.

Here's an example of how you can modify the code to implement this solution:

public function store(Request $request, PayoutUserAction $payoutUserAction)
{
    if ($request->ajax()) {
        $validator = Validator::make($request->all(), [
            'amount' => 'required|numeric',
        ]);

        if ($validator->fails()) {
            return response()->json(['errors' => $validator->errors()], 422);
        }
    } else {
        $request->validate([
            'amount' => 'required|numeric',
        ]);
    }

    // Continue with the rest of the code if validation passes
    // ...
}

By checking if the request is an AJAX request, we can handle the validation differently. If it is an AJAX request and the validation fails, we return a JSON response with the validation errors. Otherwise, we redirect back with the validation errors as usual.

Make sure to import the Validator facade at the top of your file:

use Illuminate\Support\Facades\Validator;

This solution should help you handle the validation errors consistently in both development and production environments.

Snapey's avatar

how do you arrive at the view that shows the form?

timgavin's avatar

@Snapey

Route::get('payouts', [StripePayoutController::class, 'index'])
    ->name('payouts.index')
    ->middleware(['auth', 'verified']);

Route::post('payouts', [StripePayoutController::class, 'store'])
    ->name('payouts.store')
    ->middleware(['auth', 'verified']);

The form

<form action="{{ route('payouts.store') }}" method="POST">
    @csrf
    <div class="mt-6 grid grid-cols-2 items-end gap-6">
        <div>
            <label for="total" class="relative">Amount (USD)
                <i class="pointer-events-none absolute top-12 left-3 block -translate-y-1/2 transform fa fa-dollar"></i>
                <input name="amount" type="number" step="0.05" class="w-full pl-6 input">
            </label>
        </div>
        <x-button type="submit" class="w-full">
            Request Payout
        </x-button>
    </div>
    @error ('amount')
    <p class="mt-1 text-error">
        {{ $message }}
    </p>
    @enderror
</form>
Snapey's avatar

I was asking about the route because 'back' seems to be broken.

is /dashboard also the page that a logged in user goes to if they just load the bare site address?

timgavin's avatar

@Snapey Yep, if I change public const HOME = '/dashboard' to public const HOME = '/account' we get redirected to /account.

I've cleared the caches, tried changing the nginx config, removed the middleware from the routes, changed the position of the routes, looked through my middleware and service providers, tried adding back() (yeah, it's broken), all sorts of things and I just can't figure out what's happening.

Took a screen recording and noticed that after hitting Submit, the url changes to https://mysite.com and then the redirect kicks in and it changes to https://mysite.com/dashboard. Never saw the url go to https://mysite.com/payouts though...

Edit: I also changed the app back to local and enabled debugging in .env and didn't see any errors. I'm using BugSnag and have received no errors, and nothing shows in any logs either.

timgavin's avatar

@Snapey Ok, so it turns out I'm having the same issue with other forms on the site. Livewire form validation works, but Laravel controller validation does not. Weird!

Snapey's avatar
Snapey
Best Answer
Level 122

@timgavin using the network tab in your browser dev tools, and preserve log, you can see the post and any redirects

timgavin's avatar

@Snapey Redirects are showing in the Network tab, but I don't know where in the app it's being called so I don't know where to stop it.

I searched the log for redirect and everywhere that shows up it's just an empty string: "redirectURL": "",

Since this seems to be global I've switched to testing on the support form...

Summary
URL: https://mysite.com/support
URL: https://mysite.com/
URL: https://mysite.com/dashboard
Status: 200
Source: Network
Address: 100.200.300.00:443

Request
POST /support
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Type: application/x-www-form-urlencoded
Origin: https://mysite.com
Referer: https://mysite.com/
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15

Redirect Response
302
Cache-Control: no-cache, private
Date: Mon, 16 Oct 2023 20:18:01 GMT
Location: https://mysite.com/
Referrer-Policy: strict-origin

Request
GET /
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Referer: https://mysite.com/
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15

Redirect Response
302
Cache-Control: no-cache, private
Date: Mon, 16 Oct 2023 20:18:02 GMT
Location: https://mysite.com/dashboard
Referrer-Policy: strict-origin

Request
:method: GET
:scheme: https
:authority: mysite.com
:path: /dashboard
... 
timgavin's avatar

Ok, I figured it out.

Looking at the network info I saw same-origin and strict-origin, so I edited the nginx config and changed add_header Referrer-Policy "strict-origin"; to add_header Referrer-Policy "same-origin"; and it's working.

I noticed the trailing slash on one request and not on their other, and I'm guessing that's the culprit? Also curious if this change will affect the API?

1 like

Please or to participate in this conversation.