【问题标题】:Laravel Email Verification for Vue SPAVue SPA 的 Laravel 电子邮件验证
【发布时间】:2019-12-16 06:56:48
【问题描述】:

如何使用 Vue Router 在 Vue SPA 上实现 Laravel 的电子邮件验证?

到目前为止,我已尝试通过更改 VerificationController 的验证和重新发送方法来处理电子邮件验证。然后,我创建了一个新通知并添加了用于验证的 API 路由。

当生成验证链接并发送到用户的电子邮件时,验证 url 类似于:

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

当链接被点击时,它会打开一个页面,但它在后端什么也不做,因为@verify api 路由没有被命中。

有什么建议吗?

验证控制器.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!');
    }


}

验证电子邮件.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');

【问题讨论】:

  • 您可能错过了将implements MustVerifyEmail 添加到那个。如果缺少此接口实现,邮件检查方法将不会做任何事情。你能发布或检查你的用户模型吗?
  • 为什么你认为它没有被击中?它完全匹配。你的 API 路径上有前缀吗?

标签: laravel email vue.js single-page-application email-verification


【解决方案1】:

这是我为解决问题所做的。转到 AuthServiceProvider

/**
   * Register any authentication / authorization services.
   *
   * @return void
   */
  public function boot()
  {
    $this->registerPolicies();

    //

    VerifyEmail::createUrlUsing(function ($notifiable) {
      $params = [
        "expires" => Carbon::now()
          ->addMinutes(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("verification.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 env("APP_FRONT") .
        "/auth/verify-email/" .
        $params["id"] .
        "/" .
        $params["hash"] .
        "?expires=" .
        $params["expires"] .
        "&signature=" .
        $signature;
    });
  }
}

将此添加到 api.php

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

将此添加到 VerifyEmailController.php

 /**
   * Mark the authenticated user's email address as verified.
   *
   * @param  \Illuminate\Foundation\Auth\EmailVerificationRequest  $request
   * @return \Illuminate\Http\RedirectResponse
   */
  public function __invoke(EmailVerificationRequest $request)
  {
    if ($request->user()->hasVerifiedEmail()) {
      return response()->json(
        [
          "message" => "Your'r email already verified.",
        ],
        Response::HTTP_BAD_REQUEST
      );
    }

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

    return response()->json(
      [
        "message" => "Verification complete thank you.",
      ],
      Response::HTTP_OK
    );
  }
}

前端

async verfyEmail() {
      try {
        const params = new URLSearchParams(this.$route.query)
        let res = await this.$axios.get(
          'verify-email/' +
            this.$route.params.id +
            '/' +
            this.$route.params.hash,
          { params }
        )
        this.$router.push({ name: 'platform-dashboard' })
      } catch (error) {
        console.log(error.response)
        this.$router.push({ name: 'platform-dashboard' })
      }
    }

【讨论】:

    【解决方案2】:

    解决方案非常简单。 使用temporarySignedRoute需要指定路由,默认为verification.verify,过期时间和参数。

    <?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',
               now()->addMinutes(60),
               ['id' => $notifiable->id, 'hash' => sha1($notifiable->getEmailForVerification())]
        );
    
        }
    }
    

    【讨论】:

    • 这将创建注册用户收到的电子邮件中的 URL,但没有解释如何在 VueJS 前端处理它。正如我从上一个答案中了解到的那样,来自该 URL 的参数需要在控制器中处理,但由于示例使用 Angular,具体细节尚不清楚。
    【解决方案3】:

    我的 Angular SPA 遇到了同样的问题。不确定您是否仍需要帮助,但希望我的回答能对某人有所帮助。

    所以虽然 laravel UrlGenerator::signedRoute 不够灵活(你可以订阅这个idea。不是同一种情况,但与此相关)我们必须自己实现 url 签名。

    在您的VerifyEmail 班级中:

        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);
        }
    

    之后,在您的 SPA 中,您应该获取 url 参数并调用 API 请求。我将指定 Angular 示例,但它应该很容易适应 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();
      });
    }
    

    我看到的另一种更简单的方法是生成直接 API url 并将其发送给用户。验证后只需将浏览器重定向到您的 SPA。我只是不明白为什么它在你的情况下不起作用。也许您的网络服务器配置中有一些重写规则,因此您的实际域名与您的APP_URL 不匹配?或者也许你在另一个端口提供你的 API?

    【讨论】:

      猜你喜欢
      • 2020-03-11
      • 2020-03-18
      • 2022-01-07
      • 2019-02-12
      • 2021-02-15
      • 2017-06-23
      • 1970-01-01
      • 2017-08-21
      • 1970-01-01
      相关资源
      最近更新 更多