Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

AlexOlival's avatar

Email Verification with API and Laravel Sanctum

Hey everyone. I'm currently making an API for a mobile app but I think I'm a bit confused with how email verification and authentication is meant to work. I'm attempting to implement the following flow:

  • User registers in the mobile app and it sends a request to the API
  • Laravel creates the user and fires off an email
  • User receives the email and clicks on the link
  • Laravel verifies the user and redirects them to the mobile app via deep-link

However when the user clicks the email link a "route login not defined" error is rendered. Which makes sense, because the user is not authenticated at the time. But am I getting this wrong?

Should I authenticate the user prior to sending the email? And will that work, given that we're using Sanctum rather than "regular" authentication?

Currently this is what I'm doing:

// web.php

Route::get('/email/verify/{id}/{hash}', [EmailVerificationController::class, 'verify'])
    ->middleware('signed') //note that I don't use the auth or auth:sanctum middlewares
    ->name('verification.verify');
// EmailVerificationController.php

public function verify(Request $request)
{
    $user = User::findOrFail($request->id);

    if ($user->email_verified_at) {
        return '';
    }

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

    return redirect()->away('app://open'); // The deep link
}
    	

Is there any security risk here? Should I at any point authenticate the user before or after they click the link? I wanted to avoid rendering "web views" as much as possible.

0 likes
3 replies
devingray_'s avatar
Level 8

The default set up is that the user is authenticated when the link is clicked, .

You are free to take this out of the auth middleware however there is the odd chance that someone can spoof the $request->id, I would suggest to change the id to a UUID and store in database if you are doing it without login. Similar to how the password resets work.

However with signed URL's you have some room. If you don't want to go the database route, you will atleast need to check the signature of the signed URL

if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
        throw new AuthorizationException;
    }
4 likes
AlexOlival's avatar

Thank you, this is exactly the explanation I wanted! Before when I tinkered around I also got an error related to the getKey()method but I can't remember the exact condition.

Bottom line, I ended up overriding the framework's email verification to instead send a PIN on the email which the user can then input in the app. This has the advantage of also being portable to SMS eventually.

However, later on I expect to have users who can also login in a "normal" way to a web app, so I'll have to work that out with those email links again in future but without the hassle of the mobile app.

3 likes
Halim's avatar

@AlexOlival Could you share your solution of sending a PIN, I'm facing exactly the same problem with my API, and I think using a PIN is great solution

Please or to participate in this conversation.