Laravel 5.7 signed route returns 403 invalid signature
After debugging UrlGenerator::hasValidSignature(), i ended by DD the variables inside UrlGenerator.php
like this:
public function hasValidSignature(Request $request, $absolute = true) { $url = $absolute ? $request->url() : '/'.$request->path(); //dd($url); $original = rtrim($url.'?'.Arr::query( Arr::except($request->query(), 'signature') ), '?'); dd($original); $expires = Arr::get($request->query(), 'expires'); $signature = hash_hmac('sha256', $original, call_user_func($this->keyResolver)); return hash_equals($signature, (string) $request->query('signature', '')) && ! ($expires && Carbon::now()->getTimestamp() > $expires); }
the $original
variable showed me what was actually happening with my URL, and showed this:
https://example.com/report/1/1?expires=1546586977&settings%5Bincrementing%5D=1&settings%5Bexists%5D=1&settings%5BwasRecentlyCreated%5D=0&settings%5Btimestamps%5D=1&profile%5Bincrementing%5D=1&profile%5Bexists%5D=1&profile%5BwasRecentlyCreated%5D=0&profile%5Btimestamps%5D=1&user%5Bincrementing%5D=1&user%5Bexists%5D=1&user%5BwasRecentlyCreated%5D=0&user%5Btimestamps%5D=1
as you can see there are parameters after the expires parameter, those parameter where aded after the route creation, and that was the problem, this happened because i had a middleware sharing some information to the views like this:
UserDataMiddleware.php<?phpnamespace App\Http\Middleware;use Closure;use Illuminate\Support\Facades\Auth;use App\User;use App\Setting;use App\UserProfile;use Illuminate\Support\Facades\View;class UserData{ /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if (Auth::check()) { $settings = Setting::where('user_id', Auth::user()->id)->first(); $profile = UserProfile::where('user_id', Auth::id())->first(); $user = Auth::user(); View::share('settings', $settings); //Another way to share variables, with the View::share View::share('profile', $profile); //Now we need to share owr variables trough the REQUEST to our controllers $request->merge([ 'settings' => $settings, 'profile' => $profile, 'user' => $user ]); } return $next($request); }}
this middleware was inside the middleware groups, so that was the problem hopefully if someone in the future experiments this, then it could check that first.
I just had this problem and turns out that empty parameters in the URL will never validate. So when you do this:
URL::temporarySignedRoute('newsletter.verify', now()->addDays(3), ['name' => $name, 'email' => $email])
but name is an empty string (because it's not mandatory), URL will get generated with name=
as part of query string, but this code inside Laravel
$original = rtrim($url.'?'.Arr::query(Arr::except($request->query(), 'signature')), '?');
will not return the empty name
, hence the URL was 'altered' and validation fails. The commonly used middleware ConvertEmptyStringsToNull
might have something to do with this.