Laravel 中的无密码身份验证
发布时间 作者 史蒂夫·麦克杜格尔
有时我们不希望用户拥有密码。有时我们希望向用户的电子邮件地址发送一个魔术链接,让他们点击以获得访问权限。
在本教程中,我将逐步介绍一个您可以用来自己实现此功能的过程。此工作流程的主要重点是创建一个签名 URL,它允许我们向用户的电子邮件地址发送特定 URL,并且只有该用户才能访问此 URL。
我们首先希望从我们的迁移、模型和模型工厂中删除密码字段。由于不需要此字段,我们希望确保将其删除,因为它默认情况下不是可空列。这是一个相对简单的过程,因此我不会在此部分显示任何代码示例。趁此机会,我们可以删除密码重置表,因为我们没有密码要重置。
路由应该是我们接下来要关注的内容。我们可以将登录路由创建为一个简单的视图路由,因为我们将在此示例中使用 Livewire。让我们看看如何注册此路由
Route::middleware(['guest'])->group(static function (): void { Route::view('login', 'app.auth.login')->name('login');});
我们希望将其包装在 guest 中间件中,以强制在用户已登录的情况下重定向。我不会介绍此示例的 UI,但在本教程末尾,有一个指向 GitHub 上的存储库的链接。让我们逐步介绍我们将用于登录表单的 Livewire 组件。
final class LoginForm extends Component{ public string $email = ''; public string $status = ''; public function submit(SendLoginLink $action): void { $this->validate(); $action->handle( email: $this->email, ); $this->status = 'An email has been sent for you to log in.'; } public function rules(): array { return [ 'email' => [ 'required', 'email', Rule::exists( table: 'users', column: 'email', ), ] ]; } public function render(): View { return view('livewire.auth.login-form'); }}
我们的组件有两个我们想要使用的属性。电子邮件用于捕获表单输入。然后是状态,因此我们不需要依赖于请求会话。我们有一个返回验证规则的方法。这是我在 Livewire 组件中使用验证规则的首选方法。我们的提交方法是此组件的主要方法,它是我在处理表单组件时使用的命名约定。这对我来说很有意义,但您可以随意选择适合您的命名方法。我们使用 Laravels 容器将一个操作类注入到此方法中,以共享用于创建和发送签名 URL 的逻辑。我们这里要做的就是将输入的电子邮件传递给操作,并设置一个状态以提醒用户正在发送电子邮件。
现在让我们逐步介绍我们要使用的操作。
final class SendLoginLink{ public function handle(string $email): void { Mail::to( users: $email, )->send( mailable: new LoginLink( url: URL::temporarySignedRoute( name: 'login:store', parameters: [ 'email' => $email, ], expiration: 3600, ), ) ); }}
此操作只需要发送一封电子邮件。如果需要,我们可以将其配置为排队 - 但在处理需要快速处理的操作时,如果我们正在构建 API,最好将其排队。我们有一个名为 LoginLink
的可邮寄类,我们将要使用的 URL 传递给它。我们的 URL 是通过传入我们要为其生成路由的路由名称以及要作为签名一部分使用的参数来创建的。
final class LoginLink extends Mailable{ use Queueable, SerializesModels; public function __construct( public readonly string $url, ) {} public function envelope(): Envelope { return new Envelope( subject: 'Your Magic Link is here!', ); } public function content(): Content { return new Content( markdown: 'emails.auth.login-link', with: [ 'url' => $this->url, ], ); } public function attachments(): array { return []; }}
我们的可邮寄类非常简单,与标准可邮寄类差别不大。我们传入一个表示 URL 的字符串。然后,我们希望将其传递到内容中的 markdown 视图中。
<x-mail::message># Login Link Use the link below to log into the {{ config('app.name') }} application. <x-mail::button :url="$url">Login</x-mail::button> Thanks,<br>{{ config('app.name') }}</x-mail::message>
用户将收到此电子邮件并点击链接,将他们带到签名 URL。让我们注册此路由并看看它是什么样。
Route::middleware(['guest'])->group(static function (): void { Route::view('login', 'app.auth.login')->name('login'); Route::get( 'login/{email}', LoginController::class, )->middleware('signed')->name('login:store');});
我们希望为此路由使用一个控制器,并确保我们添加了 signed 中间件。现在让我们看一下控制器,看看我们如何处理签名 URL。
final class LoginController{ public function __invoke(Request $request, string $email): RedirectResponse { if (! $request->hasValidSignature()) { abort(Response::HTTP_UNAUTHORIZED); } /** * @var User $user */ $user = User::query()->where('email', $email)->firstOrFail(); Auth::login($user); return new RedirectResponse( url: route('dashboard:show'), ); }}
我们的第一步是确保 URL 具有有效的签名,如果它没有,我们希望抛出一个未授权的响应。一旦我们知道签名有效,我们就可以查询传入的用户并对其进行身份验证。最后,我们返回一个重定向到仪表板的响应。
我们的用户现在已成功登录,我们的旅程已完成。但是,我们还需要看看注册路由。让我们接下来添加此路由。这同样将是一个视图路由。
Route::middleware(['guest'])->group(static function (): void { Route::view('login', 'app.auth.login')->name('login'); Route::get( 'login/{email}', LoginController::class, )->middleware('signed')->name('login:store'); Route::view('register', 'app.auth.register')->name('register');});
同样,我们使用 Livewire 组件用于注册表单 - 就像我们在登录过程中所做的那样。
final class RegisterForm extends Component{ public string $name = ''; public string $email = ''; public string $status = ''; public function submit(CreateNewUser $user, SendLoginLink $action): void { $this->validate(); $user = $user->handle( name: $this->name, email: $this->email, ); if (! $user) { throw ValidationException::withMessages( messages: [ 'email' => 'Something went wrong, please try again later.', ], ); } $action->handle( email: $this->email, ); $this->status = 'An email has been sent for you to log in.'; } public function rules(): array { return [ 'name' => [ 'required', 'string', 'min:2', 'max:55', ], 'email' => [ 'required', 'email', ] ]; } public function render(): View { return view('livewire.auth.register-form'); }}
我们捕获用户的姓名、电子邮件地址,并具有一个状态属性,而不是再次使用请求会话。同样,我们使用一个 rules 方法来返回此请求的验证规则。我们回到 submit 方法,这一次,我们希望注入两个操作。
CreateNewUser
是我们用来根据提供的 信息创建并返回新用户的操作。如果由于某种原因失败,我们会在电子邮件上抛出一个验证异常。然后,我们使用我们在登录表单上使用的 SendLoginLink
操作来最大限度地减少代码重复。
final class CreateNewUser{ public function handle(string $name, string $email): Builder|Model { return User::query()->create([ 'name' => $name, 'email' => $email, ]); }}
我们可以重命名登录存储路由,但从技术上来说,我们正在再次执行此操作。我们创建了一个用户。然后我们希望登录用户。
这是您可采取的众多方法之一来实现无密码身份验证,但这是一种可行的方法。您可以在 此处找到 GitHub 存储库,如果您认为可以改进它,请随时提交 PR!
技术作家,就职于 Laravel 新闻,开发者布道师,就职于 Treblle。API 专家,经验丰富的 PHP/Laravel 工程师。 YouTube 直播主。