Суть проекта такова: пользователь заполняет форму регистрации, после чего пользовательские данные заносятся в БД. Но сразу после этого пользователь не сможет зайти под своим именем на сайт, так как сначала он должен пройти проверку подлинности своего почтового ящика. Для осуществлении такой проверки, кроме стандартных полей в таблицу users (name, email, password) мы добавим 2 дполнительных поля: verified и token. Первое поле может принимать значения true/false (изначально false), второе поле служит для хранения случайного значения – ключа. На свой почтовый ящик пользователь получает сообщение со ссылкой, содержащей этот ключ из базы. После того как пользователь переходит по ссылке, ключ из ссылки и ключ из базы сравниваются, при их совпадении поле verified переходит в состояние true, а ключ удаляется. Активация считается завершенной. После этого пользователь сможет успешно пройти аутентификацию.
С использованием собственной аутентификации
Для работы с БД нам потребуется изменить стандартный файл миграции 2014_10_12_000000_create_users_table.php, добавим в него необходимые поля: verified и token. После чего содержимое метода up() будет таким:
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password', 60);
$table->boolean('verified')->default(false);
$table->string('token')->nullable();
$table->rememberToken();
$table->timestamps();
});
Перейдем в файл маршрутов (web.php). Создадим:
- маршруты (post и get) для регистрации и авторизации;
- маршрут для проверки ключа, полученного пользователем в письме, отправленном ему после регистрации;
- маршрут для выхода (logout) со страницы администратора (dashboard) и для входа на нее.
Готовый файл будет таким:
Route::get('/', function () {
return view('welcome');
});
Route::get('register', 'RegistrationController@register');
Route::post('register', 'RegistrationController@postRegister');
Route::get('register/confirm/{token}', 'RegistrationController@confirmEmail');
Route::get('login', 'SessionsController@login')->middleware('guest');
Route::post('login', 'SessionsController@postLogin')->middleware('guest');
Route::get('logout', 'SessionsController@logout');
Route::get('dashboard', ['middleware' => 'auth', function() {
return 'Добро пожаловать, '.Auth::user()->name.'!';
}]);
Далее создадим первый контроллер RegistrationController. В него добавим метод confirmEmail(), который подтверждает введенный пользователем почтовый адрес:
public function confirmEmail(Request $request, $token)
{
User::whereToken($token)->firstOrFail()->confirmEmail();
$request->session()->flash('message', 'Учетная запись подтверждена. Войдите под своим именем.');
return redirect('login');
}
Как вы могли заметить по файлу маршрутов, этот контроллер содержит еще два метода: register() и postRegister(). Первый отвечает за генерацию формы регистрации:
public function register()
{
return view('auth.register');
}
Второй за проверку данных из этой формы, заполненной пользователем:
public function postRegister(Request $request)
{
$this->validate($request, [
'name' => 'required',
'email' => 'required|email|unique:users',
'password' => 'required'
]);
$user = User::create($request->all());
Mail::to($user)->send(new UserRegistered($user));
$request->session()->flash('message', 'На ваш адрес было выслано письмо с подтверждением регистрации.');
return redirect()->back();
}
В конструкторе контроллера вызовем middleware:
public function __construct()
{
$this->middleware('guest');
}
Также не забудьте в заголовке контроллера добавить нужные строчки для классов UserRegistered и Mail. В результате чего заголовок должен выглядеть следующим образом:
namespace App\Http\Controllers;
use App\User;
use App\Mail\UserRegistered;
use Illuminate\Support\Facades\Mail;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
Второй из нужных нам контроллеров, это SessionsController. В нем создадим 5 методов. Первый метод – это метод с формой для входа:
public function login()
{
return view('auth.login');
}
Второй для обработки данных с этой формы:
public function postLogin(Request $request)
{
$this->validate($request, ['email' => 'required|email', 'password' => 'required']);
if ($this->signIn($request)) {
$request->session()->flash('message', 'Добро пожаловать!');
return redirect()->intended('dashboard');
}
$request->session()->flash('message', 'Вход запрещен!');
return redirect()->back();
}
Третий для выхода (logout):
public function logout(Request $request)
{
Auth::logout();
$request->session()->flash('message', 'Вы разлогинились.');
return redirect('login');
}
Четвертый и пятый для проверки данных из формы (используются во втором методе):
protected function signIn(Request $request)
{
return Auth::attempt($this->getCredentials($request), $request->has('remember'));
}
protected function getCredentials(Request $request)
{
return [
'email' => $request->input('email'),
'password' => $request->input('password'),
'verified' => true
];
}
В первом контроллере RegistrationController есть строчка:
Mail::to($user)->send(new UserRegistered($user));
С помощью нее мы отправляем письмо пользователю после заполнения формы регистрации. Начиная с версии 5.3 для отправки писем можно воспользоваться классом Mailable. Подробнее об отправке писем можно почитать в документации, в разделе Mail. Чтобы создать свой mailable-класс (в нашем случае класс с именем UserRegistered) нужно выполнить команду:
php artisan make:mail UserRegistered
После чего в директории проекта появится новая папка Mail, а в ней файл UserRegistered.php, с mailable-классом. Для нашего проекта содержимое класса UserRegistered будет следующим:
use Queueable, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function build()
{
return $this->from('[email protected]')
->view('emails.confirm');
}
В методе build() мы используем вид confirm.blade.php, в котором содержится структура письма. Структура может быть такая:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Активация регистрации нового ползователя</title>
</head>
<body>
<h1>Спасибо за регистрацию, {{$user->name}}!</h1>
<p>
Перейдите <a href='{{ url("register/confirm/{$user->token}") }}'>по ссылке </a>чтобы завершить регистрацию!
</p>
</body>
</html>
Переходим к модели User. В модели нам потребуется три метода. Первый – это boot()-метод, который выполняется при загрузке модели. С помощью него мы создаем ключ подтверждения, отправляемый пользователю. Необходимый метод будет выглядеть следующим образом:
public static function boot()
{
parent::boot();
static::creating(function ($user) {
$user->token = str_random(30);
});
}
Во втором методе мы хэшируем пароль пользователя:
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
В третьем методе, при успешной валидации пользователя мы удаляем ключ из базы и задаем статус поля verified равным true:
public function confirmEmail()
{
$this->verified = true;
$this->token = null;
$this->save();
}
Осталось создать виды. Напоминаю, что для письма вид уже был создан. Основной вид приложения (используется Bootstrap):
<html>
<head>
<meta charset="UTF-8">
<title>E-mail активация</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
@include('flash')
@yield('content')
</div>
</body>
</html>
В этом виде используется вложенный вид flash для вывода сообщений:
@if (session()->has('message'))
<div class="alert alert-info">{{ session('message') }}</div>
@endif
Вид с формой для регистрации:
@extends('app')
@section('content')
<h1>Регистрация</h1>
@include('errors')
<form method="POST" action='{{ url("register/") }}'>
{!! csrf_field() !!}
<div class="form-group">
<label for="name">Имя:</label>
<input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}">
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="text" name="email" id="email" class="form-control" value="{{ old('email') }}">
</div>
<div class="form-group">
<label for="password">Пароль:</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Зарегистрироваться</button>
</div>
</form>
@stop
Вид с формой для входа на сайт:
@extends('app')
@section('content')
<h1>Авторизация</h1>
@include('errors')
<form method="POST" action='{{ url("login/") }}'>
{{csrf_field()}}
<div class="form-group">
<label for="email">Email:</label>
<input type="text" name="email" id="email" class="form-control" value="{{ old('email') }}">
</div>
<div class="form-group">
<label for="password">Пароль:</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember" id="remember" {{ old('remember') ? ' checked' : '' }}> Запомнить меня
</label>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Войти</button>
</div>
</form>
@stop
Оба вида (регистрации и авторизации) используют вложенный вид errors:
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
С использованием аутентификации из коробки
Для тестирования кода использовался Laravel 5.5, но я думаю подойдут и версии 5.3 или 5.4. Так как здесь мы используем аутентификацию из коробки, то ее нужно сначала установить, делается это с помощью команды:
php artisan make:auth
В результате чего вы получите, готовую к использованию, систему регистрации и авторизации пользователей, а также набор необходимых контроллеров и видов для возможной ее кастомизации. В данном примере мы это и сделаем, кастомизируем предлагаемый функционал регистрации и авторизации, и добавим в него возможность подтверждения регистрации по e-mail. Для этого, как было сказано вначале, нам нужно добавить 2 дополнительных поля в таблицу users (verified и token). У нас уже есть стандартный файл миграции create_users_table.php, можно добавить 2 новых поля прямо в него, но у кого-то миграция уже может быть проведена и таблицы в БД уже созданы, поэтому удобнее было бы просто добавить 2 новых поля к существующей таблице. Так мы и поступим. Для добавления двух новых полей к таблице нам нужно создать дополнительный файл миграции, который это делает. Для его создания потребуется следующая команда:
php artisan make:migration add_verified_token_fields_to_users_table —table=users
После этого в папке migrations появится новый файл. Откроем его и в метод up() добавим следующее содержимое:
Schema::table('users', function (Blueprint $table) {
$table->boolean('verified')->default(false);
$table->string('token')->nullable();
});
Теперь осталось провести миграцию командой:
php artisan migrate
С БД закончили, теперь переходим к добавлению нового функционала. По традиции начнем с маршрутов. Откроем файл web.php и добавим в него маршрут, по которому будет осуществляться сверка ключа (token) из пользовательской ссылки и ключа в БД.
Route::get('register/confirm/{token}', 'Auth\RegisterController@confirmEmail');
Как видим за данную проверку должен отвечать метод confirmEmail() контроллера Auth\RegisterController. Сам контроллер у нас уже есть, поэтому откроем его и добавим необходимый метод. Выглядеть он должен следующим образом:
public function confirmEmail(Request $request, $token)
{
User::whereToken($token)->firstOrFail()->confirmEmail();
$request->session()->flash('message', 'Учетная запись подтверждена. Войдите под своим именем.');
return redirect('login');
}
Продолжим работу с контроллером. В заголовке добавим следующие строчки:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Mail\UserRegistered;
В последней строчке UserRegistered – это mailable-класс, который будет отвечать за отправку писем. Мы создадим его позже, потому что в контроллер нам нужно добавить еще один метод. Это метод register(), отвечающий за сам процесс регистрации. Выглядеть он должен следующим образом:
public function register(Request $request)
{
$this->validator($request->all())->validate();
$user = $this->create($request->all());
Mail::to($user)->send(new UserRegistered($user));
$request->session()->flash('message', 'На ваш адрес было выслано письмо с подтверждением регистрации.');
return back();
}
Здесь мы создаем новую запись в таблице users и отправляем письмо-подтверждение регистрации. Важный момент в том, что при создании новой записи туда еще нужно добавить случайное значение в поле token. Делается это с помощью метода boot() модели User. Откроем модель и добавим туда этот метод.
public static function boot()
{
parent::boot();
static::creating(function ($user) {
$user->token = str_random(30);
});
}
Также в модель мы должны добавить метод, который будет обнулять ключ и устанавливать значение поля verified равным true, в случае, если пользователь успешно подтвердит свой e-mail адрес.
public function confirmEmail()
{
$this->verified = true;
$this->token = null;
$this->save();
}
С моделью закончили. Теперь нам нужно создать mailable-класс отправки письма UserRegistered, который мы используем в контроллере Auth\RegisterController. Для этого воспользуемся следующей командой:
php artisan make:mail UserRegistered
После чего в директории проекта появится новая папка Mail, а в ней файл UserRegistered.php, с mailable-классом. Для нашего проекта содержимое класса UserRegistered будет следующим:
use Queueable, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function build()
{
return $this->to($this->user->email)
->view('auth.emails.confirm');
}
В методе build() мы используем вид confirm.blade.php (папка /resources/views/auth/emails), в котором содержится структура письма. Структура может быть такая:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Активация регистрации нового ползователя</title>
</head>
<body>
<h1>Спасибо за регистрацию, {{$user->name}}!</h1>
<p>
Перейдите <a href='{{ url("register/confirm/{$user->token}") }}'>по ссылке </a>чтобы завершить регистрацию!
</p>
</body>
</html>
На данный момент мы сделали все, чтобы пользователь мог зарегистрироваться и пройти проверку подлинности, указанного во время регистрации почтового ящика. Но остался важный момент – изменить функционал предустановленной авторизации, для того чтобы на сайт могли входить только пользователи, прошедшие e-mail-проверку, то есть те пользователи, у которых поле verified равно true. Для этого откроем уже существующий Auth\LoginController. В заголовок класса добавим строчку:
use Illuminate\Http\Request;
И добавим 2 новых метода: attemptLogin() и credentials(). Второй метод вспомогательный, то есть используется в первом. Оба метода выполняют аутентификацию пользователя, если поле verified у пользователя равно true.
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->filled('remember')
);
}
protected function credentials(Request $request)
{
return array_add($request->only($this->username(), 'password'), 'verified', true);
}
Основную часть работы мы сделали, осталось добавить в виды register.blade.php и login.blade.php вывод сообщений о том, что письмо было выслано, и что учетная запись была подтверждена. Код отвечающий за вывод сообщения одинаковый для обоих видов:
@if (session()->has('message'))
<div class="alert alert-info">{{ session('message') }}</div>
@endif
После этого страница регистрации будет выглядеть так:Страница аутентификации так: