Laravel - 403 invalid signature on email verification - laravel

I have enabled Email verification on my laravel site.
If they dont do it immediatly, but after a day or two, the link times out.. and they land on a page with 403-invalid signature.
From there they cannot do anything else beside manually editing the link. this is the point where I already lost some registrations. Is there a way to:
a) make this timeout much longer
b) in case of timeout/error land on a page that actually makes sense to the user?

#Omer YILMAZ answears your "a)" call and I am answearing your "b)" call:
We should disable signed Middleware that validates the URL signature (expiration) and automatically shows 403 page for the verification route, and then we should create a custom logic to validate the URL signature doing whatever we would like if it is invalid (expired).
Laravel 5.7, 5.8, 6.x and 7.x
Changing Auth\VerificationController provided by Laravel:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
use VerifiesEmails {
verify as verifyTrait;
}
protected $redirectTo = '/';
public function __construct()
{
$this->middleware('auth');
// $this->middleware('signed')->only('verify'); <-- remove this: it is causing 403 for expired email verification URLs
$this->middleware('throttle:6,1')->only('verify');
}
public function verify(Request $request)
{
if (!$request->hasValidSignature()) {
// some custom message
}
else {
return $this->verifyTrait($request);
}
}
}
Laravel 8.x
Changing the example provided by Laravel:
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\Request;
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
if (!$request->hasValidSignature()) {
// some custom message
}
else {
$request->fulfill();
return redirect('/home');
}
})->middleware(['auth', /*'signed' <-- remove this*/])->name('verification.verify');

In laravel 5.8 or above add this lines to config/auth.php
'verification' => [
'expire' => 525600, // One year in minutes
],

The following solution worked for me. Go to the next folder, app/Http/Middleware, and edit the following file TrustProxies.php. Change protected $proxies; edit for the following protected $proxies = ‘*’;
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
protected $proxies = '*';

Related

Missing required parameter for [Route: verification.verify]

On a project I have I am using Fortify as my BE. I need a multilingual app, therefore I added the
'prefix' => {locale}' to config/fortify.php.
Login, registering, and 2FA, are working ok, but the problem arrives with the email verification process.
If I try to click on the link received by email, it goes to the /email/verify and returns a forbidden page error.
Then if I request to get another verification email it returns the error displayed on the title of the question.
Probably it has something to be with the locale parameter because when I run route::list, the verification.verify route is displayed with the endpoint of {locale}/email/verify/{id}/{hash}, so I assume that the link on the request another mail is causing the error since it is referenced as /email/verify/{id}/{hash}.
So does anyone know how to change it?
Or has anyone faced a similar problem regarding Fortify and these localization routes?
What I had to do was to customize some of the default Fortify functions, extending some classes in order to add the locale parameter to them.
When a new user is registered (event) it sends the verification email (listener), so I had to configure the files involved in this flow.
<?php
namespace App\Listeners;
use Illuminate\Auth\Events\Registered;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class SendEmailVerificationNotification implements ShouldQueue
{
use Queueable;
public function handle(Registered $event)
{
if ($event->user instanceof MustVerifyEmail && ! $event->user->hasVerifiedEmail()) {
$event->user->sendCustomVerificationEmailNotification();
}
}
}
And create the function sendCustomVerificationEmailNotification on the user's model and the notification CustomVerificationNotification that will be sent.
public function sendCustomVerificationEmailNotification()
{
$this->notify(new CustomVerificationNotification);
}
<?php
namespace App\Notifications;
use Carbon\Carbon;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
class CustomVerificationNotification extends VerifyEmail
{
protected function verificationUrl($notifiable)
{
if (static::$createUrlCallback) {
return call_user_func(static::$createUrlCallback, $notifiable);
}
return URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'locale' => app()->getLocale(),
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
}
Then in case, the user wants an additional verification email notification, this is handled through a function on the EmailVerificationNotificationController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Http\Controllers\EmailVerificationNotificationController;
class CustomEmailVerificationController extends EmailVerificationNotificationController
{
/**
* Send a new email verification notification.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return $request->wantsJson()
? new JsonResponse('', 204)
: redirect()->intended(Fortify::redirects('email-verification'));
}
$request->user()->sendEmail();
return $request->wantsJson()
? new JsonResponse('', 202)
: back()->with('status', 'verification-link-sent');
}
}

Voyager and Jetstream: Login to Admin Panel leads to Dashboard Page

I just started my first Laravel project and try to combine Jetstream Authentification with Voyager Admin Panel.
First of all, I installed Jetstream on a fresh Laravel installation and it worked so far:
Afterwards, I tried to add Voyager to generate the CRUDs for my website and added a new user with
php artisan voyager:admin your#email.com --create
But whenever I tried to login through the url "../admin", I was redirected to "../dashboard" from Jetstream.
Even if I reentered "../admin" as URL, I was redirected. As long as I was logged in, I cannot enter the Voyager Backend.
So I guess it's some kind of routing / middleware issue, but I cannot find out which issue it is.
Within the web.php Routing file, there's only the basic stuff:
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
Route::group(['prefix' => 'admin'], function () {
Voyager::routes();
});
Not sure if that's relevant, but my IDE recognizes Voyager:: as unknown class, even it works the same way on a different Laravel installation.
But from the look of it, I expected the Route::middleware() to redirect a logged in person which types the url "../dashboard" to the Dashboard view, but nothing more. Removing this Route also didnt help the problem, so I guess I was wrong.
But beside this, only the pure Voyager Routes are left, so I'm not sure where else I can look to solve this problem.
You can add custom responses on app/Http/Responses directory.
just make new responses called LoginResponse
then use this code
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class LoginResponse implements LoginResponseContract
{
/**
* #param $request
* #return mixed
*/
public function toResponse($request)
{
$home = auth()->user()->is_admin ? '/admin' : '/dashboard';
return redirect()->intended($home);
}
}
Then, bind your LoginResponse in FortifyServiceProvider
You can use this code
<?php
namespace App\Providers;
// ...
use App\Http\Responses\LoginResponse;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// ...
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
}
}
i know that it to late but for other users who had the same problem.
first i installed jetstream
int .env file APP_URL=http://localhost:8000
i installed voyager with dummy data
i added manualy in table user_roles this ligne ( the admin )
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES ('1', '1');
and it work
you can see this video i found in youtube i think it will help you .
https://www.youtube.com/watch?v=UDYZx5uIwmQ

Laravel 8 jetstream stack inertia. Redirect to Home after Login, instead of users choice

What happens to me is that if the user puts the url: project.test/med, the system redirects to Login but then it does not redirect to dashboard but to med. In RedirectIfAuthenticated the expression Auth::guard($guard)->check() is not evaluated or at least in the Log it is not shown, so I am not able to identify what is happening.
/** RedirectIfAuthenticated.php */
public function handle($request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
   Log::info($request);
   Log::info(Auth::user());
foreach ($guards as $guard) {
     Log::info(Auth::guard($guard)->check());
     Log::info(Auth::check());
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
      }
   }
  Log::info(Auth::check());
   Log::info('end');
return $next($request);
 }
/** web.php */
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return Inertia\Inertia::render('Dashboard');
})->name('dashboard');
Route::middleware(['auth:sanctum','verified'])->get('/med', function (Request $request) {
return Inertia\Inertia::render('Med');
})->name('med');
Go to config/fortify.php and modify this line:
'home' => RouteServiceProvider::HOME,
to:
'home' => function(){
//if you want to go to a specific route
return route('dashboard');
//or if you have a bunch of redirection options
if (Auth::user()->hasRole('admin')) {
return route('admin.dashboard');
}
else{
return route('guest.dashboard');
}
}
EDIT: Fortify now ships with a simpler way of doing this. See this answer for simplified way.
The RedirectIfAuthenticated middleware does not do what you think it does. This middleware checks to see if a user is authenticated when trying to access a route with the guest middleware attached to it. So after you login to the application and you try to access /login route, this middleware will intercept the request and redirect the user to the RouteServiceProvider::HOME path.
With that being said, at the time of this writing, Laravel Fortify does not offer the ability to easily modify where the user gets redirected after registering or logging in to the application. See issue for more info
To get this to work you will have to go through a few steps.
Step 1 - Create new LoginResponse class
Anywhere in your application create a LoginResponse class and implement Laravel\Fortify\Contracts\LoginResponse.
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class LoginResponse implements LoginResponseContract
{
/**
* #inheritDoc
*/
public function toResponse($request)
{
return $request->wantsJson()
? response()->json(['two_factor' => false])
: redirect()->intended(config('fortify.home')); // This is the line you want to modify so the application behaves the way you want.
}
}
Step 2 - Override the default Fortify Login Response
In this step you have to tell Laravel to use your login response instead of the default Fortify response.
<?php
namespace App\Providers;
use App\Http\Responses\LoginResponse;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(
LoginResponseContract::class, // Contract is required in order to make this step work.
LoginResponse::class,
);
}
//...
}
Once you're done making the changes the user will be redirected to wherever you set it to be.
There is a simpler solution to this.
If you look in config/fortiy.php you will see the home variable is set to:
'home' => RouteServiceProvider::HOME,
This refers to a public const variable in app/Providers/RouteServiceProvider.php
So to redirect users to the home page after they login you can simply change the home variable:
public const HOME = '/';
Doing it this way is slightly less complex and saves you having to create a whole new login response class.
If the changes don't take effect straight away try running:
php artisan optimize
Just open fortify in the config folder
Search for 'home' => RouteServiceProvider::HOME
and replace it but your code logic and don't forget to add Auth facades
use Illuminate\Support\Facades\Auth;
like is :
'home' => function () {
$is_active = Auth::user()->is_active;
$role = Auth::user()->role;
if ($is_active == 1) {
if ($role== 1) {
return '/admin/dashboard';
} else {
return '/';
}
} else {
return '/login';
}
},

Laravel load middleware in routes under condition

I have a route files for my api and im using this package to cache the whole response.
routes/api.php
<?php
Route::get('/types', 'TypeController#indexType')->middleware('cacheResponse:600');
This works fine but i need to skip this middleware loading when i have a specific header in the request so i make this extra middleware
conditionalCacheResponse.php
<?php
namespace App\Http\Middleware;
use Closure;
class ConditionalCacheResponse
{
public function handle($request, Closure $next)
{
if (request()->header('Draft') != true) {
$this->middleware('cacheResponse:3600');
}
return $next($request);
}
}
and setup like this
routes/api.php
<?php
Route::get('/types', 'TypeController#indexType')->middleware('conditionalCacheResponse');
But is not working, im not sure if i can append a middleware in this way.
There's a few ways to do this, one way would be to use the handle() method of the HttpKernel to unset the CacheResponse middleware based on a check of the request header.
So in app/Http/Kernel.php, add this method:
public function handle($request)
{
if ($request->header('Draft')) {
if (($key = array_search('Spatie\ResponseCache\Middlewares\CacheResponse', $this->middleware)) !== false) {
unset($this->middleware[$key]);
}
}
return parent::handle($request);
}
I haven't checked this is working, but it should do the job.

Laravel TransformRequest not applying to own Request classes

This issue was fixed in Laravel 5.4.9.
The two new middleware classes doesn't apply to my own Requests.
The two new middleware are:
\App\Http\Middleware\TrimStrings::class
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class
However, they don't apply their changes to my own requests. Forexample: I have this request:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class CreateStoreRequest extends FormRequest
{
public function authorize()
{
return Auth::check();
}
public function rules()
{
var_dump( $this->all() ); // Content not handled by middleware
return [
// Rules
];
}
}
When i use var_dump, I can see that the content isn't changed.
If i use Illuminate\Http\Request in my controller it works.
Is there any way to implement the changes in my own request classes?
I found a solution in the Github rep.
Add the following to your request class.
public function all()
{
$this->merge( $this->request->all() );
return parent::all();
}
If you are checking for types in your rules, you might want to add "nullable" to the rules.

Resources