Home > Back-end >  Laravel - Alternative root domain on verification email link
Laravel - Alternative root domain on verification email link

Time:12-16

I have a Laravel application which can be accessed from multiple clients all of which have their own domain (eg myclient.com). In addition, there is a core client (for the sake of this example using the domain coreclient.org).

Users associated to the core client are able to sign up users for any of the other clients.

Admin users for any other client are able to sign up users only for their client.

This uses the base code from Fortify.

It's all fine until it comes to the account verification email when a new user is created by the core client for a different client. The link in the verification email uses the core client domain rather than client the new user belongs to.

eg http://coreclient.org/email/verify/1/dd8...8ad?expires=1639608547&signature=718...1ee

It needs to be http://myclient.com/email/verify/1/dd8...8ad?expires=1639608547&signature=718...1ee

To fix this, I replaced the domain in the url string after it is generated but this causes an invalid signature as I'm assuming this is generated based on the domain of the person creating the new user.

Here's what I have so far in the boot function of my AuthServiceProvider file... any ideas?

VerifyEmail::createUrlUsing(function ($notifiable) {

    $verifyUrl = URL::temporarySignedRoute(
        'verification.verify',
        Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
        [
            'id' => $notifiable->getKey(),
            'hash' => sha1($notifiable->getEmailForVerification()),
        ]
    );

    // This amends the domain in the URL if core client user creating for another client - creates mismatch with signature though
    if(auth()->user()->client->is_core && auth()->user()->client_id != $notifiable->client_id) {
        $verifyUrl = Str::replace(auth()->user()->client->domains->first()->domain, $notifiable->client->domains->first()->domain, $verifyUrl);
    }

    return $verifyUrl;
});

CodePudding user response:

The signature depends on your APP_KEY. To publish it to other application should not discussed. It is a no go.

You can customize with key which it should resolved with.

Paste this code in here:

App\Providers\AppServiceProvider.php

public function register()
{
  $this->app->make('url')->setKeyResolver(function () {
    return $this->app->make('config')->get('app.shared-signed-key');
  });
}

This code needs to be on the coreclient and on the myclient so they can both generate and resolve it.

You can get a deep view with following methods:

  • \Illuminate\Routing\UrlGenerator::signedRoute
  • \Illuminate\Routing\UrlGenerator::setKeyResolver
  • \Illuminate\Routing\RoutingServiceProvider::registerUrlGenerator

CodePudding user response:

I handled this situation by simply not checking the hostname when validating the URL. I wrote this custom middleware:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Exceptions\InvalidSignatureException;

class ValidateRelativeSignature
{
    /**
     * Validate a URL signature, but ignore hostname
     *
     * @param Request $request
     * @param Closure $next
     * @return mixed
     * @throws InvalidSignatureException
     */
    public function handle(Request $request, Closure $next): mixed
    {
        if ($request->hasValidSignature(false)) {
            return $next($request);
        }

        throw new InvalidSignatureException();
    }
}

The default behaviour for Request::hasValidSignature() is to validate the absolute URL; passing false as the argument changes that.

Then I simply created a custom URL generator much as you have done, but only signing part of the URL:

VerifyEmail::createUrlUsing(function (User $notifiable) {
    $url = "https://";
    $url .= $notifiable->client->domains->first()->domain;
    $url .= URL::temporarySignedRoute(
        'verification.verify',
        Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
        [
            'id' => $notifiable->getKey(),
            'hash' => sha1($notifiable->getEmailForVerification()),
        ],
        false
    );

    return $url;
});

Note again the fourth parameter passed to URL::temporarySignedRoute ensures the created route is a relative one. As you can see, the domain is simply prepended to the relative URL.

The middleware can be applied in routes/web.php or in the controller. We do the latter:

class VerificationController extends Controller
{
    use VerifiesEmails;

    /**
     * Create a new controller instance.
     */
    public function __construct()
    {
        $this->middleware('auth');
        $this->middleware(ValidateRelativeSignature::class)->only('verify');
        $this->middleware('throttle:6,1')->only('verify', 'resend');
    }
}
  • Related