Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel 8 rest api email verification

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

like image 768
Gothiquo Avatar asked Dec 14 '20 08:12

Gothiquo


People also ask

How do I verify my email after registration email verification Laravel 8 authentication and mailing?

Just click the Verify Email Address button, and you will be redirected automatically to the '/home' route. This home route is protected by middlewares to make sure that only a user with verified email can access this route.


2 Answers

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 :)

like image 64
Matthieu Gellé Avatar answered Oct 10 '22 09:10

Matthieu Gellé


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);
}
like image 38
Bens Avatar answered Oct 10 '22 11:10

Bens