Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel Email Verification for Vue SPA

How can I implement Laravel's Email Verification on a Vue SPA with Vue Router?

So far I have tried to handle email verification by altering the VerificationController verify and resend methods. I then created a new notification and added API routes for the Verification.

When the verification link is generated and sent to user's email, the verification url is something like:

https://foobar.test/email/verify/1?expires=1565276056&signature=b15ccd7d6198bdcf81eea4f5cb441efe8eb2d6d5b57a1ce0b1171e685613d917

When the link is clicked, it opens up a page but it does nothing on the backend as the @verify api route is not hit.

Any suggestions?

VerificationController.php

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use Illuminate\Http\Request;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Validation\ValidationException;


class VerificationController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Email Verification Controller
    |--------------------------------------------------------------------------
    |
    | This controller is responsible for handling email verification for any
    | user that recently registered with the application. Emails may also
    | be re-sent if the user didn't receive the original email message.
    |
    */

    use VerifiesEmails;

    /**
     * Where to redirect users after verification.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api');
        $this->middleware('signed')->only('verify');
        $this->middleware('throttle:600,1')->only('verify', 'resend');
    }

    /**
     * Show the email verification notice.
     *
     */
    public function show()
    {
        //
    }

    /**
     * Mark the authenticated user's email address as verified.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function verify(Request $request)
    {
      $userID = $request[‘id’];
      $user = User::findOrFail($userID);
      $user->email_verified_at = date("Y-m-d g:i:s");
      $user->save();

      return response()->json('Email verified!');
    }

    /**
     * Resend the email verification notification.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function resend(Request $request)
    {
        if ($request->user()->hasVerifiedEmail()) {
            return response()->json('The email is already verified.', 422);
        }

        $request->user()->sendEmailVerificationNotification();

        return response()->json('We have e-mailed your verification link!');
    }


}

VerifyEmail.php

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;


use Illuminate\Support\Facades\URL;
use Carbon\Carbon;


use Illuminate\Auth\Notifications\VerifyEmail as VerifyEmailBase;


class VerifyEmail extends VerifyEmailBase
{

    /**
     * Get the verification URL for the given notifiable.
     *
     * @param  mixed  $notifiable
     * @return string
     */
    protected function verificationUrl($notifiable)
    {
      return URL::temporarySignedRoute(
      ‘verification.verify’, Carbon::now()->addMinutes(60), [‘id’ => $notifiable->getKey()]
      );

    }
}

Api.php

Route::get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
Route::get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
like image 577
Adnan Avatar asked Aug 08 '19 14:08

Adnan


1 Answers

Faced the same issue with my angular SPA. Not sure you still need help but hope my answer will help somebody.

So while laravel UrlGenerator::signedRoute isn't flexible enough (You can subscribe to this idea. Not the same case, but related to this) we have to implement url signature on our own.

In your VerifyEmail class:

    protected function verificationUrl($notifiable)
    {
        // collect and sort url params
        $params = [
            'expires' => Carbon::now()
                ->addMinutes(Config::get('auth.verification.expire', 60))
                ->getTimestamp(),
            'id' => $notifiable->getKey(),
            'hash' => sha1($notifiable->getEmailForVerification()),
        ];
        ksort($params);

        // then create API url for verification. my API have `/api` prefix,
        // so i don't want to show that url to users 
        $url = URL::route(
            'api:auth:verify',
            $params,
            true
        );

        // get APP_KEY from config and create signature
        $key = config('app.key');
        $signature = hash_hmac('sha256', $url, $key);

        // generate url for yous SPA page to send it to user
        return url('verify-email') . '?' . http_build_query($params + compact('signature'), false);
    }

After that in your SPA you should get url params and invoke API request. I'll specify Angular example but it should be easy to adapt it to Vue.

// on component load
ngOnInit() {

  // get query params from current route   
  this.route.queryParamMap.subscribe(params => {

    // generate API url. Make sure your query params come in the same order
    // as in signature generation. By default signature check middleware 
    // extracts `signature` param so `expires` is the only param that
    // is checked so order doesn't matter, but if you need another params -
    // it can turn into a problem 
    const url = this.router.createUrlTree(['api', 'auth', 'verify', data.id, data.hash],
      {queryParams: {expires: data.expires, signature: data.signature}}).toString();

    // make API request. if signature check fails - you will receive 403 error
    return this.http.get(url).subscribe();
  });
}

Another easier way i see is to generate direct API url and send it to user as you did. And after verifying just redirect browser to your SPA. I just can't understand why it don't work in your case. Maybe you have some rewrite rules in your webserver config so your actual domain name doesn't match with your APP_URL? Or maybe you serve your API in another port?

like image 86
zoryamba Avatar answered Sep 30 '22 13:09

zoryamba