Блог на Laravel 7, часть 2. Регистрация и аутентификация, восстановление пароля
05.12.2020
Теги: Laravel • MySQL • Web-разработка • Аутентификация • БазаДанных • Блог • Пользователь • Практика • Форма • Фреймворк • ШаблонСайта
Вообще, готовых пакетов для аутентификации существует великое множество, но мы все сделаем сами, чтобы хорошенько разобраться, как это работает. Нам нужно будет создать контроллеры: RegisterController
— для регистрации, LoginController
— для аутентификации, ForgotPasswordController
— для восстановления пароля.
Layout-шаблон
Создаем шаблон resources/views/layout/site.blade.php
:
<!doctype html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>{{ $title ?? env('APP_NAME') }}</title> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> <script src="{{ asset('js/app.js') }}"></script> </head> <body> <div class="container"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <!-- Логотип и кнопка «Гамбургер» --> <a class="navbar-brand" href="/">Блог</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-blog" aria-controls="navbar-blog" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <!-- Основная часть меню (может содержать ссылки, формы и прочее) --> <div class="collapse navbar-collapse" id="navbar-blog"> <!-- Этот блок расположен слева --> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="#">Блог</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Теги</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Контакты</a> </li> </ul> <!-- Этот блок расположен посередине --> <form class="form-inline my-2 my-lg-0"> <input class="form-control mr-sm-2" type="search" placeholder="Поиск по блогу" aria-label="Search"> <button class="btn btn-outline-info my-2 my-sm-0" type="submit">Искать</button> </form> <!-- Этот блок расположен справа --> <ul class="navbar-nav ml-auto"> @guest <li class="nav-item"> <a class="nav-link" href="{{ route('auth.login') }}">Войти</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ route('auth.register') }}">Регистрация</a> </li> @else <li class="nav-item"> <a class="nav-link" href="{{ route('user.index') }}">Личный кабинет</a> </li> <li class="nav-item"> <a class="nav-link" href="{{ route('auth.logout') }}">Выйти</a> </li> @endif </ul> </div> </nav> <div class="row"> <div class="col-md-3"> <h4>Категории блога</h4> <p>Здесь будут категории блога</p> <h4>Популярные теги</h4> <p>Здесь будут популярные теги</p> </div> <div class="col-md-9"> @if ($message = Session::get('success')) <div class="alert alert-success alert-dismissible mt-0" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть"> <span aria-hidden="true">×</span> </button> {{ $message }} </div> @endif @if ($errors->any()) <div class="alert alert-danger alert-dismissible mt-4" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть"> <span aria-hidden="true">×</span> </button> <ul class="mb-0"> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif @yield('content') </div> </div> </div> </body> </html>
Чтобы использовать фреймворк bootstrap, выполяняем в консоли две команды:
> composer require laravel/ui > npm install && npm run dev
Посредники auth и guest
Сразу после установки Laravel доступны посредники auth
(для аутентифицированных пользователй) и guest
(для не аутентифицированных пользователей) — они определены в файле app/Http/Kernel.php
. Мы будем использовать эти посредники, чтобы защитить маршруты контроллеров.
class Kernel extends HttpKernel { /** * 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, /* ... */ 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, /* ... */ ]; }
Если не аутентифицированный пользователь попробует перейти по маршруту, защищенному посредником auth
, он будет перенаправлен на маршрут login
. У нас такого маршрута нет, вместо него у нас будет auth.login
, так что вносим изменения в класс посредника Authenticate
.
namespace App\Http\Middleware; use Illuminate\Auth\Middleware\Authenticate as Middleware; class Authenticate extends Middleware { /** * Get the path the user should be redirected to when they are not authenticated. * * @param \Illuminate\Http\Request $request * @return string|null */ protected function redirectTo($request) { if (! $request->expectsJson()) { // не аутентифицированных пользователей отправляем // на страницу формы входа в личный кабинет return route('auth.login'); } } }
Если аутентифицированный пользователь попробует перейти по маршруту, защищенному посредником guest
, он будет перенаправлен на RouteServiceProvider::HOME
. Но у нас такой страницы нет, так что вносим изменения в класс посредника RedirectIfAuthenticated
.
namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Auth; class RedirectIfAuthenticated { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { // аутентифицированных пользователей отправляем // на главную страницу личного кабинета return redirect()->route('user.index'); } return $next($request); } }
Регистрация
1. Добавляем маршруты
Добавляем маршруты в файл routes/web.php
:
/* * Регистрация, вход в ЛК, восстановление пароля */ Route::group([ 'as' => 'auth.', // имя маршрута, например auth.index 'prefix' => 'auth', // префикс маршрута, например auth/index ], function () { // форма регистрации Route::get('register', 'Auth\RegisterController@register') ->name('register'); // создание пользователя Route::post('register', 'Auth\RegisterController@create') ->name('create'); });
2. Контроллер RegisterController
Создаем контроллер RegisterController
:
> php artisan make:controller Auth/RegisterController
namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\User; use Hash; class RegisterController extends Controller { public function __construct() { $this->middleware('guest'); } /** * Форма регистрации */ public function register() { return view('auth.register'); } /** * Добавление пользователя */ public function create(Request $request) { $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:8|confirmed', ]); User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); return redirect() ->route('auth.login') ->with('success', 'Вы успешно зарегистрировались'); } }
Все маршруты контроллера защищены посредником guest
, так что аутентифицированный пользователь не сможет перейти по ним.
3. Форма регистрации
Создаем шаблон resources/views/auth/register.blade.php
:
@extends('layout.site', ['title' => 'Регистрация']) @section('content') <h1>Регистрация</h1> <form method="post" action="{{ route('auth.register') }}"> @csrf <div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Имя, Фамилия" required maxlength="255" value="{{ old('name') ?? '' }}"> </div> <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="password" placeholder="Придумайте пароль" required maxlength="255" value=""> </div> <div class="form-group"> <input type="text" class="form-control" name="password_confirmation" placeholder="Пароль еще раз" required maxlength="255" value=""> </div> <div class="form-group"> <button type="submit" class="btn btn-info text-white">Регистрация</button> </div> </form> @endsection
Аутентификация
1. Добавляем маршруты
Добавляем маршруты в файл routes/web.php
:
/* * Регистрация, вход в ЛК, восстановление пароля */ Route::group([ 'as' => 'auth.', // имя маршрута, например auth.index 'prefix' => 'auth', // префикс маршрута, например auth/index ], function () { // форма регистрации Route::get('register', 'Auth\RegisterController@register') ->name('register'); // создание пользователя Route::post('register', 'Auth\RegisterController@create') ->name('create'); // форма входа Route::get('login', 'Auth\LoginController@login') ->name('login'); // аутентификация Route::post('login', 'Auth\LoginController@authenticate') ->name('auth'); // выход Route::get('logout', 'Auth\LoginController@logout') ->name('logout'); }); /* * Личный кабинет пользователя */ Route::group([ 'as' => 'user.', // имя маршрута, например user.index 'prefix' => 'user', // префикс маршрута, например user/index 'namespace' => 'User', // пространство имен контроллеров 'middleware' => ['auth'] // один или несколько посредников ], function () { // главная страница Route::get('index', 'IndexController')->name('index'); });
2. Контроллер LoginController
Создаем контроллер LoginController
:
> php artisan make:controller Auth/LoginController
namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Auth; class LoginController extends Controller { public function __construct() { $this->middleware('guest', ['except' => 'logout']); } /** * Форма входа в личный кабинет */ public function login() { return view('auth.login'); } /** * Аутентификация пользователя */ public function authenticate(Request $request) { $request->validate([ 'email' => 'required|string|email', 'password' => 'required|string', ]); $credentials = $request->only('email', 'password'); if (Auth::attempt($credentials)) { return redirect() ->route('user.index') ->with('success', 'Вы вошли в личный кабинет'); } return redirect() ->route('auth.login') ->withErrors('Неверный логин или пароль'); } /** * Выход из личного кабинета */ public function logout() { Auth::logout(); return redirect() ->route('auth.login') ->with('success', 'Вы вышли из личного кабинета'); } }
Все маршруты контроллера (за исключением метода logout()
) защищены посредником guest
, так что аутентифицированный пользователь не сможет перейти по ним.
3. Форма входа
Создаем шаблон resources/views/auth/login.blade.php
:
@extends('layout.site', ['title' => 'Вход в личный кабинет']) @section('content') <h1>Вход в личный кабинет</h1> <form method="post" action="{{ route('auth.auth') }}"> @csrf <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="password" placeholder="Ваш пароль" required maxlength="255" value=""> </div> <div class="form-group"> <button type="submit" class="btn btn-info text-white">Войти</button> </div> </form> @endsection
4. Личный кабинет
Создаем контроллер IndexController
:
> php artisan make:controller User/IndexController --invokable
namespace App\Http\Controllers\User; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class IndexController extends Controller { /** * Handle the incoming request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function __invoke(Request $request) { return view('user.index'); } }
Создаем шаблон resources/views/user/index.blade.php
@extends('layout.site', ['title' => 'Личный кабинет']) @section('content') <h1>Личный кабинет</h1> <p>Добрый день {{ auth()->user()->name }}!</p> <p>Это личный кабинет пользователя сайта.</p> @endsection
Все маршруты контроллера защищены посредником auth
, так что попасть в личный кабинет может только аутентифицированный пользователь.
Восстановление пароля
1. Добавляем маршруты
Добавляем маршруты в файл routes/web.php
:
/* * Регистрация, вход в ЛК, восстановление пароля */ Route::group([ 'as' => 'auth.', // имя маршрута, например auth.index 'prefix' => 'auth', // префикс маршрута, например auth/index ], function () { // форма регистрации Route::get('register', 'Auth\RegisterController@register') ->name('register'); // создание пользователя Route::post('register', 'Auth\RegisterController@create') ->name('create'); // форма входа Route::get('login', 'Auth\LoginController@login') ->name('login'); // аутентификация Route::post('login', 'Auth\LoginController@authenticate') ->name('auth'); // выход Route::get('logout', 'User\LoginController@logout') ->name('logout'); // форма ввода адреса почты Route::get('forgot-password', 'Auth\ForgotPasswordController@form') ->name('forgot-form'); // письмо на почту Route::post('forgot-password', 'Auth\ForgotPasswordController@mail') ->name('forgot-mail'); });
2. Контроллер ForgotPasswordController
Создаем контроллер ForgotPasswordController
:
> php artisan make:controller Auth/ForgotPasswordController
namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; class ForgotPasswordController extends Controller { public function __construct() { $this->middleware('guest'); } /** * Форма ввода адреса почты */ public function form() { return view('auth.forgot'); } /** * Письмо на почту для восстановления */ public function mail(Request $request) { $request->validate([ 'email' => 'required|email|exists:users', ]); $token = Str::random(60); DB::table('password_resets')->insert( ['email' => $request->email, 'token' => $token, 'created_at' => Carbon::now()] ); // ссылка для сброса пароля $link = route('auth.reset-form', ['token' => $token, 'email' => $request->email]); Mail::send( 'email.reset-password', ['link' => $link], function($message) use ($request) { $message->to($request->email); $message->subject('Восстановление пароля'); } ); return back()->with('success', 'Ссылка для восстановления пароля отправлена на почту'); } }
Все маршруты контроллера защищены посредником guest
, так что аутентифицированный пользователь не сможет перейти по ним.
3. Форма ввода адреса
Создаем шаблон resources/views/auth/forgot.blade.php
:
@extends('layout.site', ['title' => 'Восстановление пароля']) @section('content') <h1>Восстановление пароля</h1> <form method="post" action="{{ route('auth.forgot-mail') }}"> @csrf <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? '' }}"> </div> <div class="form-group"> <button type="submit" class="btn btn-info text-white">Восстановить</button> </div> </form> @endsection
4. Шаблон для письма
Создаем файл resources/views/email/reset-password.blade.php
:
<h3>Восстановление пароля</h3> <p>Для восстановления пароля перейдите по этой <a href="{{ $link }}">ссылке</a>.</p>
4. Перевод на русский
При вводе адреса почты не зарегистрированного пользователя получаем такое сообщение об ошибке:
В настройках config/app.php
у меня указан русский язык, но файлов перевода нет. Строка validation.exists
означает, что должно быть показано сообщение exists
из файла resources/lang/ru/validation.php
. Установим языковый пакет
> composer require laravel-lang/lang:~7.0
После чего скопируем vendor/laravel-lang/lang/src/ru
в директорию resources/lang
.
5. Отправка письма
На этапе разработки письма отправлять не будем, вместо этого будем их записывать в log-файл. Для этого редактируем файл конфигурации .env
:
MAIL_MAILER=log MAIL_FROM_ADDRESS=noreply@example.com MAIL_FROM_NAME="${APP_NAME}"
config/mail.php
, поэтому при отправке письма мы не используем метод from()
для установки адреса почты и имени отправителя письма.
Возьмем из базы данных адрес почты любого пользователя и попробуем восстановить пароль:
Проверяем log-файл — письмо, содержащее ссылку для восстановления пароля, было отправлено:
6. Добавляем маршруты
Добавляем маршруты в файл routes/web.php
:
/* * Регистрация, вход в ЛК, восстановление пароля */ Route::group([ 'as' => 'auth.', // имя маршрута, например auth.index 'prefix' => 'auth', // префикс маршрута, например auth/index ], function () { // форма регистрации Route::get('register', 'Auth\RegisterController@register') ->name('register'); // создание пользователя Route::post('register', 'Auth\RegisterController@create') ->name('create'); // форма входа Route::get('login', 'Auth\LoginController@login') ->name('login'); // аутентификация Route::post('login', 'Auth\LoginController@authenticate') ->name('auth'); // выход Route::get('logout', 'Auth\LoginController@logout') ->name('logout'); // форма ввода адреса почты Route::get('forgot-password', 'Auth\ForgotPasswordController@form') ->name('forgot-form'); // письмо на почту Route::post('forgot-password', 'Auth\ForgotPasswordController@mail') ->name('forgot-mail'); // форма восстановления пароля Route::get( 'reset-password/token/{token}/email/{email}', 'Auth\ResetPasswordController@form' )->name('reset-form'); // восстановление пароля Route::post('reset-password', 'Auth\ResetPasswordController@reset') ->name('reset-password'); });
7. Контроллер ResetPasswordController
Создаем контроллер ResetPasswordController
:
> php artisan make:controller Auth/ResetPasswordController
namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\User; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Hash; class ResetPasswordController extends Controller { public function __construct() { $this->middleware('guest'); } /** * Форма ввода нового пароля */ public function form($token, $email) { return view('auth.reset-password', compact('token', 'email')); } /** * Установка нового пароля */ public function reset(Request $request) { $request->validate([ 'email' => 'required|email|exists:users', 'password' => 'required|string|min:8|confirmed', ]); // удаляем старые записи из таблицы сброса паролей $expire = Carbon::now()->subMinute(60); DB::table('password_resets') ->where('created_at', '<', $expire) ->delete(); // если ссылка на восстановления была отправлена $row = DB::table('password_resets') ->where([ 'email' => $request->email, 'token' => $request->token, ]) ->first(); // если ссылка уже устарела, то ничего не делаем if(!$row) { return back()->withErrors('Ссылка восстановления пароля устарела'); } // устанавливаем новый пароль для пользователя User::where('email', $request->email) ->update(['password' => Hash::make($request->password)]); // удаляем пользователя из таблицы сброса паролей DB::table('password_resets')->where(['email'=> $request->email])->delete(); return redirect() ->route('auth.login') ->with('success', 'Ваш пароль был успешно изменен'); } }
Все маршруты контроллера защищены посредником guest
, так что аутентифицированный пользователь не сможет перейти по ним.
8. Форма ввода пароля
Создаем файл resources/views/auth/reset-password.blade.php
:
@extends('layout.site', ['title' => 'Сбросить пароль']) @section('content') <h1>Сбросить пароль</h1> <form method="post" action="{{ route('auth.reset-password') }}"> @csrf <input type="hidden" name="email" value="{{ $email }}"> <input type="hidden" name="token" value="{{ $token }}"> <div class="form-group"> <input type="text" class="form-control" name="password" placeholder="Придумайте пароль" required maxlength="255" value=""> </div> <div class="form-group"> <input type="text" class="form-control" name="password_confirmation" placeholder="Пароль еще раз" required maxlength="255" value=""> </div> <div class="form-group"> <button type="submit" class="btn btn-info text-white">Сбросить</button> </div> </form> @endsection
- Блог на Laravel 7, часть 3. Checkbox «Запомнить меня» и подтверждение адреса почты
- Мини-блог на Laravel, часть 8. Регистрация и аутентификация пользователей на сайте
- Блог на Laravel 7, часть 11. Панель управления — назначение ролей и прав для пользователей
- Блог на Laravel 7, часть 10. Личный кабинет — CRUD-операции над постами и комментариями
- Блог на Laravel 7, часть 9. Панель управления — создание, публикация, удаление комментариев
- Блог на Laravel 7, часть 8. Панель управления — CRUD для категорий, тегов и пользователей
- Блог на Laravel 7, часть 7. Панель управления — создание, публикация, удаление постов
Поиск: Laravel • MySQL • Web-разработка • Аутентификация • База данных • Блог • Пользователь • Практика • Форма • Фреймворк • Шаблон сайта