14

How to remake Laravel 5.7 Email Verification for Rest API?

Or is it worth doing everything from scratch?

  • Well I implemented a section of your code, and noticed that you're using POST routes instead of GET? The email sends the params as a GET, and would throw an unknown method exception in that event. – Austin Sweat Oct 10 '18 at 16:15
  • I do not remember why I did it. I redid my question in response and changed the routes. Thanks! – Илья Зеленько Oct 10 '18 at 17:12

2 Answers2

37

This case works for me. Full project code here.

1) Redesigned VerificationController controller

Removed redirects and made response()->json(...) responses.

<?php

namespace App\Http\Controllers\API\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Verified;

class VerificationController extends Controller
{
    use VerifiesEmails;

    /**
     * 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)
    {
        // ->route('id') gets route user id and getKey() gets current user id() 
        // do not forget that you must send Authorization header to get the user from the request
        if ($request->route('id') == $request->user()->getKey() &&
            $request->user()->markEmailAsVerified()) {
            event(new Verified($request->user()));
        }

        return response()->json('Email verified!');
//        return redirect($this->redirectPath());
    }

    /**
     * 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('User already have verified email!', 422);
//            return redirect($this->redirectPath());
        }

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

        return response()->json('The notification has been resubmitted');
//        return back()->with('resent', true);
    }

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

2) Added my Notification:

I made it so that the link in the email message led to my frontend and contained a temporarySignedRoute link for the request.

use Illuminate\Auth\Notifications\VerifyEmail as VerifyEmailBase;

class VerifyEmail extends VerifyEmailBase
{
//    use Queueable;

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

        // I use urlencode to pass a link to my frontend.
        return $prefix . urlencode($temporarySignedURL);
    }
}

3) Added config frontend.php:

return [
    'url' => env('FRONTEND_URL', 'http://localhost:8080'),
    // path to my frontend page with query param queryURL(temporarySignedRoute URL)
    'email_verify_url' => env('FRONTEND_EMAIL_VERIFY_URL', '/verify-email?queryURL='),
];

4) Added to User model:

use App\Notifications\VerifyEmail;

and

/**
 * Send the email verification notification.
 *
 * @return void
 */
public function sendEmailVerificationNotification()
{
    $this->notify(new VerifyEmail); // my notification
}

5) Added routes

The following routes are used in Laravel:

// Email Verification Routes...
Route::get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
Route::get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
Route::get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');

They are added to the application if used Auth::routes();.

As far as I understand the email/verify route and its method in the controller are not needed for Rest API.

6) On my frontend page /verify-email(from frontend.php config) i make a request to the address contained in the parameter queryURL

The received URL looks like this:

"http://localhost:8000/api/email/verify/6?expires=1537122891&signature=0e439ae2d511f4a04723a09f23d439ca96e96be54f7af322544fb76e3b39dd32"

My request(with Authorization header):

await this.$get(queryURL) // typical get request

The code perfectly verify the email and I can catch the error if it has already been verified. Also I can successfully resend the message to the email.

Did I make a mistake somewhere? Also I will be grateful if you improve something.

  • Privet ;) I am new to api programming and i just wondering, is it really restful if you have auth()->user()? – b00sted 'snail' Nov 01 '18 at 10:01
  • 1
    @nrkz Yes! but you still need to use `tymon/jwt-auth` package (for example), it integrates with built-in authorization, so `auth()->user()` works. – Илья Зеленько Nov 01 '18 at 12:39
  • I managed to get it to work with your code. Really nice. But how do you use middleware to check if the user is verified? – Tudor-Radu Barbu Apr 08 '19 at 11:24
  • 1
    @ИльяЗеленько auth()->user()->getKey() = null i need to send the jwt token with The received URL right ? – Ahmed Aboud Oct 18 '19 at 12:47
  • 1
    @ИльяЗеленько , how can you append authorization header while verifying email, because it will be the link in mail, i can only append query parameters in it, i don't know how are you doing it? – Haritsinh Gohil Jan 25 '20 at 10:43
1

I tried Илья Зеленько answer but I must modify VerificationController construct method as follow

public function __construct()
{
    $this->middleware('auth')->except(['verify','resend']);
    $this->middleware('signed')->only('verify');
    $this->middleware('throttle:6,1')->only('verify', 'resend');
}

otherwise laravel need autentication to access verify and resend routes

Giacomo
  • 79
  • 1
  • 2
  • 1
    Maybe then you should remove `$this->middleware('auth')->except(['verify','resend']);` altogether? Since there are only two methods: `verify`, `resend`.In theory, you can also replace `$this->middleware('throttle:6,1')->only('verify', 'resend');` with `$this->middleware('throttle:6,1');` – Илья Зеленько Mar 09 '19 at 12:01
  • @ИльяЗеленько Doesn't this kind of modification impose a security risk? – Farzan Oct 18 '19 at 17:43