Laravel 8 rest api email verification
Asked Answered
T

2

13

After a huge search in the internet and in the forum, I just gave up...

I am develping a rest api using Laravel 8 and I am trying since week to make the email verification working using the officiel documentation for that, the email is always sent successfully once the user is registered event(new Registered($user));
The problem is that once I click on the link in the received email, I got redirected to the login page (which in this case is a post call)..

Here my routes/api.php:

Route::group(['namespace' => 'App\Http\Controllers', 'middleware' => ['api'], 'prefix' => 'auth'], function ($router) {
    Route::post('login', 'AuthController@login')->name('login');
    Route::post('register', 'AuthController@register');
    Route::post('logout', 'AuthController@logout');
    Route::post('profile', 'AuthController@profile')->middleware('verified');
    Route::post('refresh', 'AuthController@refresh');
});

Route::group(['namespace' => 'App\Http\Controllers', 'middleware' => ['api']],function ($router) {
    Route::get('/email/verify/{id}/{hash}', 'VerificationController@verify')->middleware(['auth', 'signed'])->name('verification.verify');
    Route::get('/email/resend', 'VerificationController@resend')->middleware(['auth', 'throttle:6,1'])->name('verification.send');
});

And here my VerificationController:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\EmailVerificationRequest;

class VerificationController extends Controller
{
    public function resend(Request $request)
    {
        $request->user()->sendEmailVerificationNotification();
        return response()->json(['message' => __('auth.email_sent')], Response::HTTP_NO_CONTENT);
    }

    public function verify(EmailVerificationRequest $request)
    {
        $request->fulfill();
        return response()->json(['message' => __('auth.user_verified_successfully')], Response::HTTP_RESET_CONTENT);
    }
}

Last but not least, I added the LogVerifiedUser event to EventServiceProvider as required.

Any suggestion plz? I tried to remove the middleware auth from verify route, but it doesn't help me...

PS: I am using JWT for authentication

Topi answered 14/12, 2020 at 8:7 Comment(2)
Are you using a front end SPA (React/Vue/Angular) or normal Multi Page app ? If it is a multi page app, probably your web middleware is redirecting you to login page. Check your web.phpDeer
I am using a front end SPA (angular)Topi
D
40

I had to develop exactly the same functionality for my rest laravel 8 api, I share my work with you, hoping to be able to help you.

To begin, your problem is that the user is redirected to the login page after clicking on the verification link. But the question is has the user been marked as verified in the database when he click ?

If it is marked as verified in the database after the click, the functionality is working but the problem is the redirection. Because if you are using a Rest API you would probably want the user to be redirected to a login or success page of your frontend application.

The last problem is your middleware. First in the api.php file the middleware for the connection is 'auth:api' instead of 'auth'. But for once you do not have to put middleware on the verification route otherwise you will have to have the user connect so that he validates his email and since you go through an API route it is pretty boring ...

Finally here is the solution I opted for :

1. In your app/Models/User.php implements MustVerifyEmail (Normally, from what I understood, that you already did, but I prefer to put it in case if other people go through this topic)

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasFactory, Notifiable, HasApiTokens;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

2. In your app/Http/Controllers/AuthController.php add event on registered user (Normally, from what I understood, that you already did, but I prefer to put it in case if other people go through this topic)

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $validatedData = $request->validate([
            'name' => 'required|max:55',
            'email' => 'email|required|unique:users',
            'password' => 'required|confirmed'
        ]);

        $validatedData['password'] = bcrypt($request->password);

        $user = User::create($validatedData);

        event(new Registered($user));

        $accessToken = $user->createToken('authToken')->accessToken;

        return response(['user' => $user, 'access_token' => $accessToken]);
    }

    public function login(Request $request)
    {
        $loginData = $request->validate([
            'email' => 'email|required',
            'password' => 'required'
        ]);

        if (!auth()->attempt($loginData)) {
            return response(['message' => 'Invalid Credentials']);
        }

        $accessToken = auth()->user()->createToken('authToken')->accessToken;

        return response(['user' => auth()->user(), 'access_token' => $accessToken]);
    }
}

3. In your routes/api.php defines this routes :


// Verify email
Route::get('/email/verify/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
    ->middleware(['signed', 'throttle:6,1'])
    ->name('verification.verify');

// Resend link to verify email
Route::post('/email/verify/resend', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();
    return back()->with('message', 'Verification link sent!');
})->middleware(['auth:api', 'throttle:6,1'])->name('verification.send');

4. Create app/Http/Controllers/VerifyEmailController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Auth\Events\Verified;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use App\Models\User;

class VerifyEmailController extends Controller
{

    public function __invoke(Request $request): RedirectResponse
    {
        $user = User::find($request->route('id'));

        if ($user->hasVerifiedEmail()) {
            return redirect(env('FRONT_URL') . '/email/verify/already-success');
        }

        if ($user->markEmailAsVerified()) {
            event(new Verified($user));
        }

        return redirect(env('FRONT_URL') . '/email/verify/success');
    }
}

Explanations:

With this solution we keep all the operation of checking the official documentation by email. Except that instead of checking if the user is connected to retrieve it and put his email in verified. We launch a method in a controller which will find the corresponding user to put it in verified.

I hope I was understandable and that it can help you :)

Darwindarwinian answered 30/1, 2021 at 23:28 Comment(6)
Where do you get VerifyEmailController from? Its not there initially in a new Laravel project.Jolyn
@Jolyn you can find it in the answer 4. Create app/Http/Controllers/VerifyEmailController.phpSedan
@Sedan so this is an invention of someone? Because at least in Laravel 8 it does not come out-of-the-boxJolyn
yes @sba, apparently is made by Matthieu Gellé, but I have been working this week in some project using Laravel 8 and React and all this stuff was a headache for me when I tried to take the API approach, so what I did is to add Inertia.js to the project, it seems that Laravel Auth is designed for web not api, now everything is easierSedan
i have absolutely no idea why does Laravel by default allow only verification of logged-in users. i could register on a mobile and then come back in different pc/browser redirected from email client at any time. this makes absolutely no sense, laravelAnta
Thank you, you saved my day. I had the same issue using Breeze, and it works pretty similar.Spurling
M
1

That's because in register() method you don't logged in the user immediately after registering the user, when the user click the link in the email, laravel auth middleware detect that current user who visit the link is not authenticated, so it redirect the user to login route. To solve this problem refer to @Matthieu Gelle answer but customizes it as follows:

in step number 2 just add this code

Auth::login($user);

below event(new Registered($user));

in step 3 use this middleware:

->middleware(['auth', 'signed'])->name('verification.verify');

for those who use sanctum:

->middleware(['auth:sanctum', 'signed'])->name('verification.verify');

and change method name from '__invoke' to 'verifyEmail'

in step 4 use this method:

public function verifyEmail(\Illuminate\Foundation\Auth\EmailVerificationRequest $request)
{
    $request->fulfill();
    return response()->json(['code' => 200, 'message' => "Verified successfully"], 200);
}
Microeconomics answered 16/3, 2022 at 8:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.