Я использую Laravel 6.x в качестве серверной части с внешним (другим доменом) Vue интерфейсом и не имею функции регистрации пользователя. Я регистрирую пользователей путем импорта групп пользователей с помощью пакета Maatwebsite / Laravel -Excel, который отлично работает.
Таким образом, когда каждый пользователь создается, задание создается путем отправки каждому пользователю ссылки для подтверждения электронной почты , который при первом входе в систему им нужно будет изменить свой пароль, и одновременно их адрес электронной почты будет помечен как проверенный, что также должно работать нормально.
Проблема в том, что с уже созданной фабрикой пользователей, которые заполнено поле email_verified_at - и недавно импортированные пользователи - я не могу войти в систему, так как пользовательское промежуточное ПО EnsureEmailApiIsVerified не имеет доступа к $ request-> user (). Я выяснил, что могу указать защиту аутентификации для api, например $ request-> user ('api'), которая затем может забрать пользователя, но только его токен-носитель (с использованием Laravel Passport) отправляется с запрос.
Это имеет смысл, поскольку иначе система могла бы узнать, кто является пользователем запроса, без какого-либо идентификатора, такого как токен. Но как тогда стандарт 'реализует MustVerifyEmail' в модели User, а последующее стандартное промежуточное ПО EnsureEmailIsVerified на веб-маршрутах выбирает $ request-> user ()? (стандартное и мое пользовательское) промежуточное ПО должно иметь доступ к $ request-> user () или оба не должны иметь доступа.
Теперь мне пришлось изменить и перенести довольно много контроллеров фреймворка в мой каталог App \ Http но я скопировал их почти дословно, просто изменив несколько вещей, чтобы убедиться, что он работает с моими маршрутами API - потому что установка защиты по умолчанию на 'api' вместо 'web' в config / auth. php не использовал его по умолчанию во всех моих контроллерах, как и предполагалось.
Итак, вот шаги, которые я выполнил:
Создал пользовательское промежуточное ПО и прикрепил его ко всей группе промежуточного программного обеспечения api в App \ Http \ Kernel. php
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class
],
'api' => [
'throttle:60,1',
'bindings',
'verifiedapi',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'role' => \App\Http\Middleware\HasRole::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'verifiedapi' => \App\Http\Middleware\EnsureApiEmailIsVerified::class,
];
Тогда вот это настраиваемое промежуточное ПО EnsureApiEmailIsVerified:
<?php
namespace App\Http\Middleware;
use Closure;
use App\Http\Controllers\API\Auth\MustVerifyApiEmail;
class EnsureApiEmailIsVerified
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (! $request->user('api') ||
($request->user('api') instanceof MustVerifyApiEmail &&
! $request->user('api')->hasVerifiedEmail())) {
return abort(403, 'Your email has not yet been verified.');
}
return $next($request);
}
}
Вы увидите эту ссылку Это экземпляр моего настраиваемого свойства 'MustVerifyApiEmail', который является чертой, которая используется в пользовательской модели, с единственным отклонением от стандартной черты, являющейся publi c функцией 'sendApiEmailVerificationNotification' как таковой:
<?php
namespace App\Http\Controllers\API\Auth;
use App\Notifications\VerifyApiEmail;
trait MustVerifyApiEmail
{
/**
* Determine if the user has verified their email address.
*
* @return bool
*/
public function hasVerifiedEmail()
{
return ! is_null($this->email_verified_at);
}
/**
* Mark the given user's email as verified.
*
* @return bool
*/
public function markEmailAsVerified()
{
return $this->forceFill([
'email_verified_at' => $this->freshTimestamp(),
])->save();
}
/**
* Send the email verification notification.
*
* @return void
*/
public function sendApiEmailVerificationNotification()
{
$this->notify(new VerifyApiEmail);
}
/**
* Get the email address that should be used for verification.
*
* @return string
*/
public function getEmailForVerification()
{
return $this->email;
}
}
Этот новый 'sendApiEmailVerificationNotification ()' уведомляет пользователя $ request ('api') с помощью настраиваемого уведомления VerifyApiEmail, как такового:
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Config;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class VerifyApiEmail implements ShouldQueue
{
use Queueable;
/**
* The callback that should be used to build the mail message.
*
* @var \Closure|null
*/
public static $toMailCallback;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$verificationUrl = $this->verificationUrl($notifiable);
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $verificationUrl);
}
return (new MailMessage)
->subject(Lang::get('Verify Email Address'))
->line(Lang::get('Please click the button below to verify your email address.'))
->action(Lang::get('Verify Email Address'), $verificationUrl)
->line(Lang::get('If you did not create an account, no further action is required.'));
}
/**
* Get the verification URL for the given notifiable.
*
* @param mixed $notifiable
* @return string
*/
protected function verificationUrl($notifiable)
{
return URL::temporarySignedRoute(
'verification.api.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
'id' => $notifiable->getKey(),
'hash' => sha1($notifiable->getEmailForVerification()),
]
);
}
/**
* Set a callback that should be used when building the notification mail message.
*
* @param \Closure $callback
* @return void
*/
public static function toMailUsing($callback)
{
static::$toMailCallback = $callback;
}
}
Новые маршруты api следующие:
Route::namespace('API\Auth')->group(function () {
Route::post('login', 'PassportController@login');
Route::post('refresh', 'PassportController@refresh');
Route::post('logout', 'PassportController@logout');
Route::get('email/verify/{id}/{hash}', 'VerificationApiController@verify')->name('verification.api.verify');
Route::get('email/resend', 'VerificationApiController@resend')->name('api.verification.resend');
});
И VerificationApiController выглядит следующим образом:
<?php
namespace App\Http\Controllers\API\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Auth\Access\AuthorizationException;
class VerificationApiController extends Controller
{
/**
* Show the email verification notice.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function show(Request $request)
{
//
}
/**
* Mark the authenticated user's email address as verified.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function verify(Request $request)
{
if (! hash_equals((string) $request->route('id'), (string) $request->user('api')->getKey())) {
throw new AuthorizationException;
}
if (! hash_equals((string) $request->route('hash'), sha1($request->user('api')->getEmailForVerification()))) {
throw new AuthorizationException;
}
if ($request->user('api')->hasVerifiedEmail()) {
return response()->json(['error' => 'Email already verified'], 422);
}
if ($request->user('api')->markEmailAsVerified()) {
event(new Verified($request->user('api')));
}
return response()->json(['success' => 'Email verified!']);
}
/**
* Resend the email verification notification.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function resend(Request $request)
{
$request->user('api')->sendApiEmailVerificationNotification();
return response()->json(['success' => 'Email verification has been resent!']);
}
}
Я также заметил, что в пользовательской модели он расширяет User as Authenticatable, который затем использует стандартный MustVerifyEmail '- поэтому я вынес это из фреймворка как ну и изменил использование на новый MustVerifyApiEmail - вот так:
<?php
namespace App\Http\Controllers\API\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use App\Http\Controllers\API\Auth\MustVerifyApiEmail;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyApiEmail;
}
Моя модель пользователя тогда выглядит так вверху:
<?php
namespace App;
use Illuminate\Support\Str;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use App\Http\Controllers\API\Auth\User as Authenticatable;
use App\Http\Controllers\API\Auth\MustVerifyApiEmailInterface;
class User extends Authenticatable implements MustVerifyApiEmailInterface
{
use HasApiTokens, Notifiable;
...
Как видите, это довольно много настройки - но все это должно работать теоретически, и я не получаю ошибок, которые можно было бы использовать. Вот ошибки, которые я получаю:
Когда я вхожу в систему с пользователем, электронная почта которого проверена или даже не проверена, я получаю сообщение об ошибке, что электронная почта пользователя не была проверена - но только потому, что он не выбирает вверх по $ request-> user ('api'). Когда я пытаюсь выбросить ошибку в самом промежуточном программном обеспечении перед возвратом запроса, сбрасывающего $ request-> user ('api'), он дает мне значение null
Итак, мой вопрос в стандартном промежуточном программном обеспечении «проверено» => \ Illuminate \ Auth \ Middleware \ EnsureEmailIsVerified :: class, - как это подбирает $ request-> user ()?
Я что-то упускаю или я поступаю неправильно? Кажется, что когда пользователь входит в систему, он не регистрирует их, а затем запускает промежуточное программное обеспечение - поэтому нет $ request-> user ('api') - возможно, потому что я использую Passport, но я бы подумал, что то, что должно случается, что промежуточное ПО должно запускаться после аутентификации пользователя, тогда оно будет иметь доступ к $ request-> user ('api')
ЛЮБЫЕ УКАЗАНИЯ БУДУТ ОЧЕНЬ ПРИНЦИПИРОВАНЫ!
API. php
Route::prefix('email')->middleware(['auth:api'])->group(function () {
Route::get('resend', 'VerificationController@resend')->name('verification.resend');
Route::get('verify/{id}/{hash}', 'VerificationController@verify')->name('verification.verify');
});