{"id":152,"date":"2025-08-26T13:49:55","date_gmt":"2025-08-26T13:49:55","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=152"},"modified":"2025-08-26T13:50:48","modified_gmt":"2025-08-26T13:50:48","slug":"implementing-password-reset-in-laravel-12-without-packages","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/implementing-password-reset-in-laravel-12-without-packages\/","title":{"rendered":"Implementing Password Reset in Laravel 12 Without Packages"},"content":{"rendered":"\n<p>A great user experience includes a safe way to recover accounts. In this guide, you\u2019ll build a complete <strong>Password Reset flow in Laravel 12 \u2014 without using packages<\/strong>. We\u2019ll cover the database, routes, controller, mailer, Blade UI, and important security measures like token hashing, throttling, expirations, and single-use links. Every block includes explanations so beginners won\u2019t get lost.<\/p>\n\n\n\n<p>By the end, users will be able to request a reset link, receive an email with a secure one-time URL, and set a new password \u2014 all implemented with clean Laravel conventions.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; What is a Password Reset flow?<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>A password reset flow lets a user prove ownership of their account (via email) and set a new password even if they forgot the old one. The core idea: the app generates a <strong>short-lived, single-use token<\/strong>, sends it to the user\u2019s email, and only someone with access to that mailbox can redeem it to change the password. We\u2019ll build this end to end with secure defaults.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 &#8211; Prerequisites<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Laravel 12 project (PHP 8.2+) with a working database connection in <code>.env<\/code><\/li>\n<li>Users table with <code>email<\/code> and <code>password<\/code> columns<\/li>\n<li>Mail configured in <code>.env<\/code> (SMTP, Mailgun, etc.) so the app can send emails<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Create the password reset table<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span>php artisan make:migration create_password_resets_table\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Edit the migration to store the email, a <em>hashed<\/em> token (safer than plain text), and a timestamp to expire tokens.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ database\/migrations\/xxxx_xx_xx_xxxxxx_create_password_resets_table.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Migrations<\/span>\\<span class=\"hljs-title\">Migration<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Schema<\/span>\\<span class=\"hljs-title\">Blueprint<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Schema<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Migration<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">up<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        Schema::create(<span class=\"hljs-string\">'password_resets'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>            $table-&gt;string(<span class=\"hljs-string\">'email'<\/span>)-&gt;index();\n<\/span><\/span><span class='shcb-loc'><span>            $table-&gt;string(<span class=\"hljs-string\">'token'<\/span>);             <span class=\"hljs-comment\">\/\/ store hashed token here<\/span>\n<\/span><\/span><span class='shcb-loc'><span>            $table-&gt;timestamp(<span class=\"hljs-string\">'created_at'<\/span>)-&gt;nullable();\n<\/span><\/span><span class='shcb-loc'><span>        });\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">down<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        Schema::dropIfExists(<span class=\"hljs-string\">'password_resets'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>};\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We\u2019ll store a <strong>hashed<\/strong> version of the token (HMAC) so if the DB were leaked, attackers cannot use reset links. The raw token only lives in the user\u2019s email link.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span>php artisan migrate\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Configure Mail (so emails actually send)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># .env (example for SMTP)<\/span>\nMAIL_MAILER=smtp\nMAIL_HOST=smtp.yourprovider.com\nMAIL_PORT=<span class=\"hljs-number\">587<\/span>\nMAIL_USERNAME=your_user\nMAIL_PASSWORD=your_password\nMAIL_ENCRYPTION=tls\nMAIL_FROM_ADDRESS=no-reply@your-domain.com\nMAIL_FROM_NAME=<span class=\"hljs-string\">\"Your App\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Make sure these values are correct for your provider. Incorrect mail settings are the #1 reason reset flows \u201cdon\u2019t work\u201d.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Routes (guest-only flow with throttling)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">Auth<\/span>\\<span class=\"hljs-title\">PasswordResetController<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Route<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ Show \"Forgot Password\" form &amp; send reset link<\/span>\n<\/span><\/span><span class='shcb-loc'><span>Route::middleware(&#91;<span class=\"hljs-string\">'guest'<\/span>, <span class=\"hljs-string\">'throttle:6,1'<\/span>])-&gt;group(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    Route::get(<span class=\"hljs-string\">'\/forgot-password'<\/span>, &#91;PasswordResetController::class, <span class=\"hljs-string\">'requestForm'<\/span>])\n<\/span><\/span><span class='shcb-loc'><span>        -&gt;name(<span class=\"hljs-string\">'password.request'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    Route::post(<span class=\"hljs-string\">'\/forgot-password'<\/span>, &#91;PasswordResetController::class, <span class=\"hljs-string\">'sendLink'<\/span>])\n<\/span><\/span><span class='shcb-loc'><span>        -&gt;name(<span class=\"hljs-string\">'password.email'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ User lands here from email link (contains token)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    Route::get(<span class=\"hljs-string\">'\/reset-password\/{token}'<\/span>, &#91;PasswordResetController::class, <span class=\"hljs-string\">'resetForm'<\/span>])\n<\/span><\/span><span class='shcb-loc'><span>        -&gt;name(<span class=\"hljs-string\">'password.reset'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ Submit new password<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    Route::post(<span class=\"hljs-string\">'\/reset-password'<\/span>, &#91;PasswordResetController::class, <span class=\"hljs-string\">'reset'<\/span>])\n<\/span><\/span><span class='shcb-loc'><span>        -&gt;name(<span class=\"hljs-string\">'password.update'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>What this does<\/strong>: all four routes are guest-only and rate-limited (6 attempts\/minute) to reduce abuse. The GET routes show forms; the POST routes perform actions.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; Mailable for the reset link<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">php artisan make:mail ResetPasswordMail --markdown=mail.reset-password<\/code><\/span><\/pre>\n\n\n<p>This creates a mailable and a Markdown email template. We\u2019ll pass a signed, single-use style URL (with raw token) to the email template.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ app\/Mail\/ResetPasswordMail.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Mail<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Bus<\/span>\\<span class=\"hljs-title\">Queueable<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Mail<\/span>\\<span class=\"hljs-title\">Mailable<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Queue<\/span>\\<span class=\"hljs-title\">SerializesModels<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ResetPasswordMail<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Mailable<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Queueable<\/span>, <span class=\"hljs-title\">SerializesModels<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> string $url;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">__construct<\/span><span class=\"hljs-params\">(string $url)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">$this<\/span>-&gt;url = $url;\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">build<\/span><span class=\"hljs-params\">()<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">$this<\/span>-&gt;subject(<span class=\"hljs-string\">'Reset your password'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>            -&gt;markdown(<span class=\"hljs-string\">'mail.reset-password'<\/span>, &#91;\n<\/span><\/span><span class='shcb-loc'><span>                <span class=\"hljs-string\">'url'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;url\n<\/span><\/span><span class='shcb-loc'><span>            ]);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span>&lt;!-- resources\/views\/mail\/reset-password.blade.php --&gt;\n<\/span><\/span><span class='shcb-loc'><span>@component(<span class=\"hljs-string\">'mail::message'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\"># Reset your password<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>Click the button below to choose a <span class=\"hljs-keyword\">new<\/span> password. <span class=\"hljs-keyword\">If<\/span> you didn\u2019t request this, you can ignore this email.\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>@component(<span class=\"hljs-string\">'mail::button'<\/span>, &#91;<span class=\"hljs-string\">'url'<\/span> =&gt; $url])\n<\/span><\/span><span class='shcb-loc'><span>Reset Password\n<\/span><\/span><span class='shcb-loc'><span>@endcomponent\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>This link will expire shortly <span class=\"hljs-keyword\">for<\/span> your security.\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>Thanks,\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>{{ config(<span class=\"hljs-string\">'app.name'<\/span>) }}\n<\/span><\/span><span class='shcb-loc'><span>@endcomponent\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>How it works<\/strong>: the controller will generate a raw token (random string), store a hashed version in DB, and include the raw token in the URL emailed to the user. The form that receives the token will recompute the hash and compare to DB.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7 &#8211; Controller: generate, email, verify, and reset<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/Auth\/PasswordResetController.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">Auth<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">Controller<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Mail<\/span>\\<span class=\"hljs-title\">ResetPasswordMail<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">User<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">DB<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Hash<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Mail<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Str<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PasswordResetController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ 1) Show \"Forgot Password\" form<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">requestForm<\/span><span class=\"hljs-params\">()<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'auth.forgot-password'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ 2) Accept email, generate token, store hashed token, email link<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">sendLink<\/span><span class=\"hljs-params\">(Request $request)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        $request-&gt;validate(&#91;\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'email'<\/span> =&gt; <span class=\"hljs-string\">'required|email'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        ]);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Always respond the same to avoid leaking if email exists<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $email = (string) $request-&gt;input(<span class=\"hljs-string\">'email'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Create raw token and hashed token<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $rawToken   = Str::random(<span class=\"hljs-number\">64<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        $hashedToken = hash_hmac(<span class=\"hljs-string\">'sha256'<\/span>, $rawToken, config(<span class=\"hljs-string\">'app.key'<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Upsert the record<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        DB::table(<span class=\"hljs-string\">'password_resets'<\/span>)-&gt;updateOrInsert(\n<\/span><\/span><span class='shcb-loc'><span>            &#91;<span class=\"hljs-string\">'email'<\/span> =&gt; $email],\n<\/span><\/span><span class='shcb-loc'><span>            &#91;<span class=\"hljs-string\">'token'<\/span> =&gt; $hashedToken, <span class=\"hljs-string\">'created_at'<\/span> =&gt; now()]\n<\/span><\/span><span class='shcb-loc'><span>        );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Build the URL user will click (token in path, email as query)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $url = route(<span class=\"hljs-string\">'password.reset'<\/span>, $rawToken) . <span class=\"hljs-string\">'?email='<\/span> . urlencode($email);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Send email (do not reveal whether email exists)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        Mail::to($email)-&gt;send(<span class=\"hljs-keyword\">new<\/span> ResetPasswordMail($url));\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> back()-&gt;with(<span class=\"hljs-string\">'status'<\/span>, <span class=\"hljs-string\">'If we found an account with that email, a reset link has been sent.'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ 3) Show \"Reset Password\" form (user clicked link)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">resetForm<\/span><span class=\"hljs-params\">(string $token, Request $request)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        $email = (string) $request-&gt;query(<span class=\"hljs-string\">'email'<\/span>, <span class=\"hljs-string\">''<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'auth.reset-password'<\/span>, compact(<span class=\"hljs-string\">'token'<\/span>, <span class=\"hljs-string\">'email'<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/\/ 4) Validate token, update password, delete token<\/span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">reset<\/span><span class=\"hljs-params\">(Request $request)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        $data = $request-&gt;validate(&#91;\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'email'<\/span> =&gt; <span class=\"hljs-string\">'required|email'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'token'<\/span> =&gt; <span class=\"hljs-string\">'required'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'password'<\/span> =&gt; <span class=\"hljs-string\">'required|confirmed|min:8'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        ]);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Recreate hashed token from raw token<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $hashedToken = hash_hmac(<span class=\"hljs-string\">'sha256'<\/span>, $data&#91;<span class=\"hljs-string\">'token'<\/span>], config(<span class=\"hljs-string\">'app.key'<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Look up the reset row<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $row = DB::table(<span class=\"hljs-string\">'password_resets'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>            -&gt;where(<span class=\"hljs-string\">'email'<\/span>, $data&#91;<span class=\"hljs-string\">'email'<\/span>])\n<\/span><\/span><span class='shcb-loc'><span>            -&gt;where(<span class=\"hljs-string\">'token'<\/span>, $hashedToken)\n<\/span><\/span><span class='shcb-loc'><span>            -&gt;first();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Fail if token not found or expired (e.g., &gt; 60 minutes old)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (! $row || now()-&gt;diffInMinutes($row-&gt;created_at) &gt; <span class=\"hljs-number\">60<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-keyword\">return<\/span> back()-&gt;withErrors(&#91;<span class=\"hljs-string\">'email'<\/span> =&gt; <span class=\"hljs-string\">'This reset link is invalid or has expired.'<\/span>]);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Update user password<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $user = User::where(<span class=\"hljs-string\">'email'<\/span>, $data&#91;<span class=\"hljs-string\">'email'<\/span>])-&gt;first();\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span> (! $user) {\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-comment\">\/\/ Generic failure message (don\u2019t reveal existence)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-keyword\">return<\/span> back()-&gt;withErrors(&#91;<span class=\"hljs-string\">'email'<\/span> =&gt; <span class=\"hljs-string\">'This reset link is invalid or has expired.'<\/span>]);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        $user-&gt;forceFill(&#91;\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'password'<\/span> =&gt; Hash::make($data&#91;<span class=\"hljs-string\">'password'<\/span>]),\n<\/span><\/span><span class='shcb-loc'><span>        ])-&gt;save();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Invalidate token (single-use)<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        DB::table(<span class=\"hljs-string\">'password_resets'<\/span>)-&gt;where(<span class=\"hljs-string\">'email'<\/span>, $data&#91;<span class=\"hljs-string\">'email'<\/span>])-&gt;delete();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-comment\">\/\/ Optionally logout all sessions by rotating remember_token<\/span>\n<\/span><\/span><span class='shcb-loc'><span>        $user-&gt;setRememberToken(Str::random(<span class=\"hljs-number\">60<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>        $user-&gt;save();\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> redirect()-&gt;route(<span class=\"hljs-string\">'login'<\/span>)-&gt;with(<span class=\"hljs-string\">'status'<\/span>, <span class=\"hljs-string\">'Password updated successfully. You can log in now.'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>Highlights:<\/strong> we <em>never<\/em> reveal whether an email exists; tokens are <strong>HMAC-hashed<\/strong> in DB; links expire (60 minutes example); tokens are <strong>single-use<\/strong>; we rotate the user\u2019s remember token to invalidate existing sessions.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8 &#8211; UI: the two forms (Forgot &amp; Reset)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span>&lt;!-- resources\/views\/auth\/forgot-password.blade.php --&gt;\n<\/span><\/span><span class='shcb-loc'><span>@extends(<span class=\"hljs-string\">'layouts.app'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>@section(<span class=\"hljs-string\">'content'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>&lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">container<\/span> <span class=\"hljs-title\">py<\/span>-5\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  &lt;<span class=\"hljs-title\">h1<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">h4<\/span> <span class=\"hljs-title\">mb<\/span>-3\"&gt;<span class=\"hljs-title\">Forgot<\/span> <span class=\"hljs-title\">your<\/span> <span class=\"hljs-title\">password<\/span>?&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  @<span class=\"hljs-title\">if<\/span> (<span class=\"hljs-title\">session<\/span>('<span class=\"hljs-title\">status<\/span>'))<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">alert<\/span> <span class=\"hljs-title\">alert<\/span>-<span class=\"hljs-title\">success<\/span>\"&gt;<\/span>{{ session(<span class=\"hljs-string\">'status'<\/span>) }}&lt;\/div&gt;\n<\/span><\/span><span class='shcb-loc'><span>  @<span class=\"hljs-keyword\">endif<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  &lt;form method=<span class=\"hljs-string\">\"POST\"<\/span> action=<span class=\"hljs-string\">\"{{ route('password.email') }}\"<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">card<\/span> <span class=\"hljs-title\">card<\/span>-<span class=\"hljs-title\">body<\/span>\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    @<span class=\"hljs-title\">csrf<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">label<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">label<\/span>\"&gt;<span class=\"hljs-title\">Email<\/span>&lt;\/<span class=\"hljs-title\">label<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">control<\/span> <span class=\"hljs-title\">mb<\/span>-2\" <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">email<\/span>\" <span class=\"hljs-title\">name<\/span>=\"<span class=\"hljs-title\">email<\/span>\" <span class=\"hljs-title\">value<\/span>=\"<\/span>{{ old(<span class=\"hljs-string\">'email'<\/span>) }}<span class=\"hljs-string\">\" required autocomplete=\"<\/span>email<span class=\"hljs-string\">\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    @error('email') &lt;div class=\"<\/span>text-danger small<span class=\"hljs-string\">\"&gt;{{ $message }}&lt;\/div&gt; @enderror<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;button class=\"<\/span>btn btn-primary mt<span class=\"hljs-number\">-2<\/span><span class=\"hljs-string\">\"&gt;Email me a reset link&lt;\/button&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">  &lt;\/form&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">&lt;\/div&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">@endsection<\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This is a simple, accessible form that asks for the email and shows success or validation messages. The status message is always neutral (doesn\u2019t leak if the email exists).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span>&lt;!-- resources\/views\/auth\/reset-password.blade.php --&gt;\n<\/span><\/span><span class='shcb-loc'><span>@extends(<span class=\"hljs-string\">'layouts.app'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>@section(<span class=\"hljs-string\">'content'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>&lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">container<\/span> <span class=\"hljs-title\">py<\/span>-5\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  &lt;<span class=\"hljs-title\">h1<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">h4<\/span> <span class=\"hljs-title\">mb<\/span>-3\"&gt;<span class=\"hljs-title\">Choose<\/span> <span class=\"hljs-title\">a<\/span> <span class=\"hljs-title\">new<\/span> <span class=\"hljs-title\">password<\/span>&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  @<span class=\"hljs-title\">if<\/span> (<span class=\"hljs-title\">session<\/span>('<span class=\"hljs-title\">status<\/span>'))<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">alert<\/span> <span class=\"hljs-title\">alert<\/span>-<span class=\"hljs-title\">success<\/span>\"&gt;<\/span>{{ session(<span class=\"hljs-string\">'status'<\/span>) }}&lt;\/div&gt;\n<\/span><\/span><span class='shcb-loc'><span>  @<span class=\"hljs-keyword\">endif<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  &lt;form method=<span class=\"hljs-string\">\"POST\"<\/span> action=<span class=\"hljs-string\">\"{{ route('password.update') }}\"<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">card<\/span> <span class=\"hljs-title\">card<\/span>-<span class=\"hljs-title\">body<\/span>\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    @<span class=\"hljs-title\">csrf<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">hidden<\/span>\" <span class=\"hljs-title\">name<\/span>=\"<span class=\"hljs-title\">token<\/span>\" <span class=\"hljs-title\">value<\/span>=\"<\/span>{{ $token }}<span class=\"hljs-string\">\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;label class=\"<\/span>form-label<span class=\"hljs-string\">\"&gt;Email&lt;\/label&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;input class=\"<\/span>form-control mb<span class=\"hljs-number\">-2<\/span><span class=\"hljs-string\">\" type=\"<\/span>email<span class=\"hljs-string\">\" name=\"<\/span>email<span class=\"hljs-string\">\" value=\"<\/span>{{ old(<span class=\"hljs-string\">'email'<\/span>, $email ?? <span class=\"hljs-string\">''<\/span>) }}<span class=\"hljs-string\">\" required autocomplete=\"<\/span>email<span class=\"hljs-string\">\"&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    @error('email') &lt;div class=\"<\/span>text-danger small<span class=\"hljs-string\">\"&gt;{{ $message }}&lt;\/div&gt; @enderror<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;label class=\"<\/span>form-label<span class=\"hljs-string\">\"&gt;New password&lt;\/label&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;input class=\"<\/span>form-control mb<span class=\"hljs-number\">-2<\/span><span class=\"hljs-string\">\" type=\"<\/span>password<span class=\"hljs-string\">\" name=\"<\/span>password<span class=\"hljs-string\">\" required autocomplete=\"<\/span><span class=\"hljs-keyword\">new<\/span>-password<span class=\"hljs-string\">\"&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    @error('password') &lt;div class=\"<\/span>text-danger small<span class=\"hljs-string\">\"&gt;{{ $message }}&lt;\/div&gt; @enderror<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;label class=\"<\/span>form-label<span class=\"hljs-string\">\"&gt;Confirm new password&lt;\/label&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;input class=\"<\/span>form-control mb<span class=\"hljs-number\">-2<\/span><span class=\"hljs-string\">\" type=\"<\/span>password<span class=\"hljs-string\">\" name=\"<\/span>password_confirmation<span class=\"hljs-string\">\" required autocomplete=\"<\/span><span class=\"hljs-keyword\">new<\/span>-password<span class=\"hljs-string\">\"&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;button class=\"<\/span>btn btn-success mt<span class=\"hljs-number\">-2<\/span><span class=\"hljs-string\">\"&gt;Update Password&lt;\/button&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">  &lt;\/form&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">&lt;\/div&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">@endsection<\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The reset form includes the hidden <code>token<\/code>, email, and both password fields with confirmation. Errors appear inline so users immediately know what to fix.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>9 &#8211; Extra hardening (recommended)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Token hashing:<\/strong> we used <code>hash_hmac('sha256', $rawToken, app.key)<\/code> so DB never stores the raw token.<\/li>\n<li><strong>Short expiry:<\/strong> 60 minutes is common. Tighten to 15\u201330 minutes for extra security.<\/li>\n<li><strong>Single use:<\/strong> we delete the row once used.<\/li>\n<li><strong>Neutral messages:<\/strong> always show a generic success after requesting a link to avoid email enumeration.<\/li>\n<li><strong>Session rotation:<\/strong> after reset, rotate <code>remember_token<\/code> to log out other sessions.<\/li>\n<li><strong>Queue mails:<\/strong> mark the mailable as <code>ShouldQueue<\/code> for reliability on busy apps.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>10 &#8211; Test the whole flow (step-by-step)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Visit <code>\/forgot-password<\/code>, enter your email, submit the form.<\/li>\n<li>Check your inbox and click the \u201cReset Password\u201d button.<\/li>\n<li>You\u2019ll land at <code>\/reset-password\/{token}?email=you@example.com<\/code>.<\/li>\n<li>Choose a new password and submit.<\/li>\n<li>Try logging in with the new password \u2014 the old one no longer works.<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>11 &#8211; Common errors &amp; quick fixes<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Emails aren\u2019t arriving:<\/strong> verify <code>.env<\/code> mail settings; try a known-good SMTP; check logs for auth errors.<\/li>\n<li><strong>\u201cInvalid or expired link\u201d:<\/strong> token expired or doesn\u2019t match; resend and ensure the exact emailed URL is used.<\/li>\n<li><strong>Token works twice:<\/strong> confirm you delete the row after a successful reset.<\/li>\n<li><strong>Users stuck logged in elsewhere:<\/strong> ensure <code>remember_token<\/code> is rotated after password change.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping Up<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>You built a production-ready <strong>Password Reset in Laravel 12<\/strong> without packages: secure token storage, rate-limited endpoints, full Blade UI, and strong hygiene like single-use tokens and session rotation. This improves both security and user trust while keeping the code understandable and lightweight.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What\u2019s Next<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/how-to-build-email-verification-in-laravel-12-step-by-step\">How to Build Email Verification in Laravel 12 (Step by Step)<\/a> \u2014 confirm real users before granting access.<\/li>\n<li><a href=\"\/blog\/implementing-two-factor-authentication-in-laravel\">Implementing Two-Factor Authentication in Laravel<\/a> \u2014 add an extra layer of login security.<\/li>\n<li><a href=\"\/blog\/how-to-restrict-page-access-by-role-in-laravel-12\">How to Restrict Page Access by Role in Laravel 12<\/a> \u2014 protect admin-only areas.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>A great user experience includes a safe way to recover accounts. In this guide, you\u2019ll build a complete Password Reset flow in Laravel 12 \u2014 without using packages. We\u2019ll cover the database, routes, controller, mailer, Blade UI, and important security measures like token hashing, throttling, expirations, and single-use links. Every block includes explanations so beginners [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":160,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[12,13,21],"class_list":["post-152","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-authentication","tag-login","tag-password"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/152","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=152"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/152\/revisions"}],"predecessor-version":[{"id":155,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/152\/revisions\/155"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/160"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=152"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=152"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=152"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}