Laravel. Аутентификация пользователей

23.09.2020

Теги: LaravelWeb-разработкаАутентификацияКлассПользовательФормаФреймворкШаблонСайта

Аутентификация — это процесс регистрации и логина пользователей. Не путать с авторизацией — проверкой прав уже залогиненного пользователя. В Laravel сделать аутентификацию очень просто — почти всё готово из коробки. Конфигурационный файл аутентификации расположен в config/auth.php, он содержит несколько опций для тонкой настройки служб аутентификации.

<?php
return [
    // Authentication Defaults
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

    // Authentication Guards
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    // User Providers
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    // Resetting Passwords
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    // Password Confirmation Timeout
    'password_timeout' => 10800,

];

По сути средства аутентификации Laravel состоят из «гардов» и «провайдеров».

  • Гарды определяют то, как именно аутентифицируются пользователи, для каждого запроса. Например, Laravel поставляется с гардом session, который поддерживает состояние аутентифицированности с помощью хранилища сессий и кук.
  • Провайдеры определяют то, как именно пользователи извлекаются из базы данных. Laravel поставляется с поддержкой извлечения пользователей с помощью Eloquent и конструктора запросов БД. Но при необходимости можно определить дополнительные провайдеры.

Гарды, доступные по умолчанию, имеют названия созвучные их области применения. Гард web подходит для большинства стандартных сайтов, т.к. для аутентификации пользователей используются данные, хранящиеся в сессии (физически они могут быть оформлены как в виде файлов, так и таблицы в базе данных). Гард api подходит для запросов, отправляемых через API на сервер. Аутентификация в этом случае происходит с помощью токенов — уникальных наборов символов, которые генерируются при успешном входе в систему под определенной учетрной записью.

Создание заготовок

Для создания заготовок всех необходимых для аутентификации контроллеров, шаблонов и роутов предназначен пакет laravel/ui:

> composer require laravel/ui --dev
> php artisan ui:auth
> npm install && npm run dev

Эту команду нужно использовать сразу после установки приложения laravel. Кроме роутов и шаблонов, будет создан контроллер HomeController, на который будет выполняться редирект после успешной аутентификации.

На этапе создания приложения можно указать флаг --auth и инсталлятор сразу добавит систему аутентификации:

> composer global require "laravel/installer"
> laravel new blog --auth # новое приложение

Будут созданы контроллеры:

  • RegisterController — обеспечивает регистрацию пользователей
  • LoginController — обеспечивает аутентификацию пользователей
  • ForgotPasswordController — отправляет письмо на сброс пароля
  • ResetPasswordController — содержит логику для сброса паролей

Будут созданы шаблоны:

  • auth.register — форма регистрации пользователей
  • auth.login — форма аутентификации пользователей
  • auth.passwords.email — форма для ввода адреса почты (восстановление пароля)
  • auth.passwords.reset — форма для ввода нового пароля (восстановление пароля)

Будут добавлены маршруты:

Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');

Вызов Auth::routes() добавляет сразу около дюжины маршрутов, посмотреть эти маршруты можно в классе Laravel\Ui\AuthRouteMethods.

Заготовки контроллеров

Контроллер RegisterController в сочетании с трейтом RegistersUsers показывает форму регистрации, проводит валидацию данных и создает нового пользователя и выполняет редирект на страницу server.com/home. Свойство $redirectTo определяет, куда будет перенаправлен пользователь после регистрации. Метод validator() определяет способ валидации регистрационных данных. А метод create() — как создать нового пользователя на основе полученных регистрационных данных.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller {

    use RegistersUsers;

    protected $redirectTo = RouteServiceProvider::HOME;

    public function __construct() {
        $this->middleware('guest');
    }

    protected function validator(array $data) {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }

    protected function create(array $data) {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
}
namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Events\Registered;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

trait RegistersUsers {

    use RedirectsUsers;

    public function showRegistrationForm() {
        return view('auth.register');
    }

    public function register(Request $request) {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $this->guard()->login($user);

        if ($response = $this->registered($request, $user)) {
            return $response;
        }

        return $request->wantsJson()
                    ? new JsonResponse([], 201)
                    : redirect($this->redirectPath());
    }

    protected function guard() {
        return Auth::guard();
    }

    protected function registered(Request $request, $user) {
        // .....
    }
}

Контроллер LoginController позволяет пользователю выполнить вход в систему. Он подружает трейт AuthenticatesUsers, подгружает RedirectUsers и ThrottlesLogins. Свойство $redirectTo определяет, куда будет перенаправлен пользователь после аутентификации.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller {

    use AuthenticatesUsers;

    protected $redirectTo = RouteServiceProvider::HOME;

    public function __construct() {
        $this->middleware('guest')->except('logout');
    }
}
namespace Illuminate\Foundation\Auth;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

trait AuthenticatesUsers {

    use RedirectsUsers, ThrottlesLogins;

    public function showLoginForm() {
        return view('auth.login');
    }

    public function login(Request $request) {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

    protected function validateLogin(Request $request) {
        $request->validate([
            $this->username() => 'required|string',
            'password' => 'required|string',
        ]);
    }

    protected function attemptLogin(Request $request) {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

    protected function credentials(Request $request) {
        return $request->only($this->username(), 'password');
    }

    protected function sendLoginResponse(Request $request) {
        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        if ($response = $this->authenticated($request, $this->guard()->user())) {
            return $response;
        }

        return $request->wantsJson()
                    ? new JsonResponse([], 204)
                    : redirect()->intended($this->redirectPath());
    }

    protected function authenticated(Request $request, $user) {
        // .....
    }

    protected function sendFailedLoginResponse(Request $request) {
        throw ValidationException::withMessages([
            $this->username() => [trans('auth.failed')],
        ]);
    }

    public function username() {
        return 'email';
    }

    public function logout(Request $request) {
        $this->guard()->logout();

        $request->session()->invalidate();

        $request->session()->regenerateToken();

        if ($response = $this->loggedOut($request)) {
            return $response;
        }

        return $request->wantsJson()
            ? new JsonResponse([], 204)
            : redirect('/');
    }

    protected function loggedOut(Request $request) {
        // .....
    }

    protected function guard() {
        return Auth::guard();
    }
}

Трейт ThrottlesLogins запрещает использовать форму входа пользователям с большим количеством неудачных попыток входа.

Контроллер ResetPasswordController подгружает трейт ResetsPasswords. Этот трейт показывает форму сброса пароля (метод showResetForm()), обрабатывает POST-запрос, выполянет валидацию и сбрасывает пароль.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;

class ResetPasswordController extends Controller {

    use ResetsPasswords;

    protected $redirectTo = RouteServiceProvider::HOME;
}
namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

trait ResetsPasswords {

    use RedirectsUsers;

    public function showResetForm(Request $request, $token = null) {
        return view('auth.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }

    public function reset(Request $request) {
        $request->validate($this->rules(), $this->validationErrorMessages());

        // Here we will attempt to reset the user's password. If it is successful we
        // will update the password on an actual user model and persist it to the
        // database. Otherwise we will parse the error and return the response.
        $response = $this->broker()->reset(
            $this->credentials($request), function ($user, $password) {
                $this->resetPassword($user, $password);
            }
        );

        // If the password was successfully reset, we will redirect the user back to
        // the application's home authenticated view. If there is an error we can
        // redirect them back to where they came from with their error message.
        return $response == Password::PASSWORD_RESET
                    ? $this->sendResetResponse($request, $response)
                    : $this->sendResetFailedResponse($request, $response);
    }

    protected function rules() {
        return [
            'token' => 'required',
            'email' => 'required|email',
            'password' => 'required|confirmed|min:8',
        ];
    }

    protected function validationErrorMessages() {
        return [];
    }

    protected function credentials(Request $request) {
        return $request->only(
            'email', 'password', 'password_confirmation', 'token'
        );
    }

    protected function resetPassword($user, $password) {
        $this->setUserPassword($user, $password);
        $user->setRememberToken(Str::random(60));
        $user->save();
        event(new PasswordReset($user));
        $this->guard()->login($user);
    }

    protected function setUserPassword($user, $password) {
        $user->password = Hash::make($password);
    }

    protected function sendResetResponse(Request $request, $response) {
        if ($request->wantsJson()) {
            return new JsonResponse(['message' => trans($response)], 200);
        }

        return redirect($this->redirectPath())
                            ->with('status', trans($response));
    }

    protected function sendResetFailedResponse(Request $request, $response) {
        if ($request->wantsJson()) {
            throw ValidationException::withMessages([
                'email' => [trans($response)],
            ]);
        }

        return redirect()->back()
                    ->withInput($request->only('email'))
                    ->withErrors(['email' => trans($response)]);
    }

    public function broker() {
        return Password::broker();
    }

    protected function guard() {
        return Auth::guard();
    }
}

Контроллер ForgotPasswordController подгружает трейт SendsPasswordResetEmails. Он показывает форму с помощью метода showLinkRequestForm, обрабатывает POST-запрос с помощию метода sendResetLinkEmail.

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;

class ForgotPasswordController extends Controller {

    use SendsPasswordResetEmails;
}
namespace Illuminate\Foundation\Auth;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;

trait SendsPasswordResetEmails {

    public function showLinkRequestForm() {
        return view('auth.passwords.email');
    }

    public function sendResetLinkEmail(Request $request) {
        $this->validateEmail($request);

        // We will send the password reset link to this user. Once we have attempted
        // to send the link, we will examine the response then see the message we
        // need to show to the user. Finally, we'll send out a proper response.
        $response = $this->broker()->sendResetLink(
            $this->credentials($request)
        );

        return $response == Password::RESET_LINK_SENT
                    ? $this->sendResetLinkResponse($request, $response)
                    : $this->sendResetLinkFailedResponse($request, $response);
    }

    protected function validateEmail(Request $request) {
        $request->validate(['email' => 'required|email']);
    }

    protected function credentials(Request $request) {
        return $request->only('email');
    }

    protected function sendResetLinkResponse(Request $request, $response) {
        return $request->wantsJson()
                    ? new JsonResponse(['message' => trans($response)], 200)
                    : back()->with('status', trans($response));
    }

    protected function sendResetLinkFailedResponse(Request $request, $response) {
        if ($request->wantsJson()) {
            throw ValidationException::withMessages([
                'email' => [trans($response)],
            ]);
        }

        return back()
                ->withInput($request->only('email'))
                ->withErrors(['email' => trans($response)]);
    }

    public function broker() {
        return Password::broker();
    }
}

Контроллер HomeController — после ввода логина и пароля пользователь будет отправлен на эту страницу:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller {

    public function __construct() {
        $this->middleware('auth');
    }

    public function index() {
        return view('home');
    }
}

Заготовки шаблонов

Шаблон формы для регистрации resources/views/auth/register.blade.php:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Register') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('register') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control @error('name') is-invalid @enderror"
                                       name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

                                @error('name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">
                                {{ __('E-Mail Address') }}
                            </label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
                                       name="email" value="{{ old('email') }}" required autocomplete="email">

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">
                                {{ __('Password') }}
                            </label>

                            <div class="col-md-6">
                                <input id="password" type="password" name="password" autocomplete="new-password"
                                       required class="form-control @error('password') is-invalid @enderror">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">
                                {{ __('Confirm Password') }}
                            </label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control"
                                       name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Шаблон формы для аутентификации (ввод логина и пароля) resources/views/auth/login.blade.php:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Login') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('login') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">
                                {{ __('E-Mail Address') }}
                            </label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
                                       name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">
                                {{ __('Password') }}
                            </label>

                            <div class="col-md-6">
                                <input id="password" type="password" name="password" autocomplete="current-password"
                                       required class="form-control @error('password') is-invalid @enderror">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <div class="col-md-6 offset-md-4">
                                <div class="form-check">
                                    <input class="form-check-input" type="checkbox" name="remember"
                                           id="remember" {{ old('remember') ? 'checked' : '' }}>

                                    <label class="form-check-label" for="remember">
                                        {{ __('Remember Me') }}
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-8 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Login') }}
                                </button>

                                @if (Route::has('password.request'))
                                    <a class="btn btn-link" href="{{ route('password.request') }}">
                                        {{ __('Forgot Your Password?') }}
                                    </a>
                                @endif
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Шаблон формы для ввода адреса почты при сбросе пароля resources/views/auth/password/reset.blade.php:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Reset Password') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('password.update') }}">
                        @csrf

                        <input type="hidden" name="token" value="{{ $token }}">

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">
                                {{ __('E-Mail Address') }}
                            </label>

                            <div class="col-md-6">
                                <input id="email" type="email" name="email" value="{{ $email ?? old('email') }}"
                                       class="form-control @error('email') is-invalid @enderror"
                                       required autocomplete="email" autofocus>

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">
                                {{ __('Password') }}
                            </label>

                            <div class="col-md-6">
                                <input id="password" type="password" name="password" autocomplete="new-password"
                                       required class="form-control @error('password') is-invalid @enderror">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">
                                {{ __('Confirm Password') }}
                            </label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control"
                                       name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Reset Password') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Шаблон формы для ввода нового пароля при сбросе пароля resources/views/auth/password/email.blade.php:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Reset Password') }}</div>

                <div class="card-body">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif

                    <form method="POST" action="{{ route('password.email') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">
                                {{ __('E-Mail Address') }}
                            </label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
                                       name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>

                                @error('email')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Send Password Reset Link') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Добавленные маршруты

После выполнения artisan-команды, будут добавлены всего две строки в файл routes/web.php:

Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');

Вызов Auth::routes() добавляет сразу около дюжины маршрутов, посмотреть эти маршруты можно в классе Laravel\Ui\AuthRouteMethods.

<?php
namespace Laravel\Ui;

class AuthRouteMethods {
    public function auth() {
        // authentication routes
        return function ($options = []) {
            // Login Routes...
            if ($options['login'] ?? true) {
                $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
                $this->post('login', 'Auth\LoginController@login');
            }
            
            // Logout Routes...
            if ($options['logout'] ?? true) {
                $this->post('logout', 'Auth\LoginController@logout')->name('logout');
            }

            // Registration Routes...
            if ($options['register'] ?? true) {
                $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
                $this->post('register', 'Auth\RegisterController@register');
            }

            // Password Reset Routes...
            if ($options['reset'] ?? true) {
                $this->resetPassword();
            }

            // Password Confirmation Routes...
            if ($options['confirm'] ??
                class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) {
                $this->confirmPassword();
            }

            // Email Verification Routes...
            if ($options['verify'] ?? false) {
                $this->emailVerification();
            }
        };
    }

    public function resetPassword() {
        // reset password routes
        return function () {
            $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
            $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
            $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
            $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
        };
    }

    public function confirmPassword() {
        // confirm password routes
        return function () {
            $this->get('password/confirm', 'Auth\ConfirmPasswordController@showConfirmForm')->name('password.confirm');
            $this->post('password/confirm', 'Auth\ConfirmPasswordController@confirm');
        };
    }

    public function emailVerification() {
        // email verification routes
        return function () {
            $this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
            $this->get('email/verify/{id}/{hash}', 'Auth\VerificationController@verify')->name('verification.verify');
            $this->post('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
        };
    }
}

Все готово, можно проверять

Теперь пользователи могут регистрироваться и аутентифицироваться. Если открыть в браузере страницу http://server.com/register, то будет показана форма регистрации нового пользователя.

После успешной аутентификации пользователь будет отправлен на страницу http://server.com/home:

Форма для входа в систему будет расположена по адресу http://server.com/login:

Форма для восстановления пароля будет расположена по адресу http://server.com/password/reset:

Перевод на русский язык

Чтобы не переводить все вручную, можно взять готовый перевод, для этого надо установить пакет

> composer require laravel-lang/lang:~7.0

После этого скопировать директорию vendor/laravel-lang/lang/src/ru и файл vendor/laravel-lang/lang/json/ru.json в директорию resources/lang.

Файл resources/lang/ru/auth.php

return [
    /*
    |--------------------------------------------------------------------------
    | Языковые ресурсы аутентификации
    |--------------------------------------------------------------------------
    |
    | Следующие языковые ресурсы используются во время аутентификации для
    | различных сообщений которые мы должны вывести пользователю на экран.
    | Вы можете свободно изменять эти языковые ресурсы в соответствии
    | с требованиями вашего приложения.
    |
    */

    'failed'   => 'Неверное имя пользователя или пароль.',
    'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте еще раз через :seconds секунд.',
];

Файл resources/lang/ru/passwords.php

return [
    /*
    |--------------------------------------------------------------------------
    | Языковые ресурсы напоминания пароля
    |--------------------------------------------------------------------------
    |
    | Последующие языковые строки возвращаются брокером паролей на неудачные
    | попытки обновления пароля в таких случаях, как ошибочный код сброса
    | пароля или неверный новый пароль.
    |
    */

    'reset'     => 'Ваш пароль был сброшен!',
    'sent'      => 'Ссылка на сброс пароля была отправлена!',
    'throttled' => 'Пожалуйста, подождите перед повторной попыткой.',
    'token'     => 'Ошибочный код сброса пароля.',
    'user'      => 'Не удалось найти пользователя с указанным электронным адресом.',
];

Файл resources/lang/ru.json

{
  "A fresh verification link has been sent to your email address.": "Новая ссылка для подтверждения была отправлена на Ваш email-адрес.",
  "All rights reserved.": "Все права защищены.",
  "Before proceeding, please check your email for a verification link.": "Прежде чем продолжить, пожалуйста, проверьте ссылку подтверждения в своей электронной почте.",
  "click here to request another": "нажмите здесь, чтобы запросить еще раз",
  "Confirm Password": "Подтверждение пароля",
  "Dashboard": "Главная",
  "E-Mail Address": "Адрес почты",
  "Error": "Ошибка",
  "Forbidden": "Запрещено",
  "Forgot Your Password?": "Забыли пароль?",
  "Go Home": "Домой",
  "Hello!": "Здравствуйте!",
  "hi": "привет",
  "If you did not create an account, no further action is required.": "Если Вы не создавали учетную запись, никаких дополнительных действий не требуется.",
  "If you did not receive the email": "Если Вы не получили письмо",
  "If you did not request a password reset, no further action is required.": "Если Вы не запрашивали сброс пароля, то дополнительных действий не требуется.",
  "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Если у Вас возникли проблемы с кликом по кнопке \":actionText\", скопируйте и вставьте адрес\nв адресную строку браузера:",
  "Invalid signature.": "Неверная подпись.",
  "Login": "Войти",
  "Logout": "Выйти",
  "Name": "Имя",
  "Not Found": "Не найдено",
  "Oh no": "О, нет",
  "Page Expired": "Страница устарела",
  "Page Not Found": "Страница не найдена",
  "Password": "Пароль",
  "Please click the button below to verify your email address.": "Пожалуйста, нажмите кнопку ниже, чтобы подтвердить свой адрес электронной почты.",
  "Please confirm your password before continuing.": "Пожалуйста, подтвердите Ваш пароль перед продолжением.",
  "Regards": "С уважением",
  "Register": "Регистрация",
  "Remember Me": "Запомнить меня",
  "Reset Password": "Сбросить пароль",
  "Reset Password Notification": "Уведомление сброса пароля",
  "Send Password Reset Link": "Отправить ссылку для сброса пароля",
  "Server Error": "Ошибка сервера",
  "Service Unavailable": "Сервис недоступен",
  "This action is unauthorized.": "Неавторизованное действие.",
  "Sorry, the page you are looking for could not be found.": "Извините, страница, которую вы ищете, не найдена.",
  "Sorry, you are forbidden from accessing this page.": "Извините, вам запрещено пользоваться этой страницей.",
  "Sorry, you are making too many requests to our servers.": "Извините, вы делаете слишком много запросов на наши серверы.",
  "Sorry, you are not authorized to access this page.": "К сожалению, у вас нет доступа к этой странице.",
  "Sorry, your session has expired. Please refresh and try again.": "К сожалению, срок действия вашей сессии истек. Обновите и повторите попытку.",
  "Sorry, we are doing some maintenance. Please check back soon.": "Извините, мы проводим некоторые работы. Пожалуйста, зайдите позже.",
  "This password reset link will expire in :count minutes.": "Срок действия ссылки для сброса пароля истекает через :count минут.",
  "Toggle navigation": "Переключить навигацию",
  "Too Many Attempts.": "Слишком много попыток.",
  "Too Many Requests": "Слишком много запросов",
  "Unauthorized": "Не авторизован",
  "Verify Email Address": "Подтвердить email-адрес",
  "Verify Your Email Address": "Подтвердите Ваш email-адрес",
  "We won't ask for your password again for a few hours.": "Мы не будем запрашивать Ваш пароль вновь в течение нескольких часов.",
  "You are logged in!": "Вы вошли в систему!",
  "You are receiving this email because we received a password reset request for your account.": "Вы получили это письмо, потому что мы получили запрос на сброс пароля для Вашей учетной записи.",
  "Whoops!": "Упс!",
  "Your email address is not verified.": "Ваш email адрес не подтвержден.",
  "Whoops, something went wrong on our servers.": "Упс, на наших серверах что-то пошло не так."
}

Модель App\User

Сразу после установки Laravel доступна миграция create_users_table и модель App\User. Код миграции показывает, какие поля будут созданы.

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration {
    public function up() {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    public function down() {
        Schema::dropIfExists('users');
    }
}

Класс модели на первый взгляд выглядит несложно, но он расширяет класс Illuminate\Foundation\Auth\User, который подгружает несколько трейтов.

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable {
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}
namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;

class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract {
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}
В Laravel вместо слова «интерфейс» часто используют слово «контракт». Это связано с тем, что почти все интерфейсы находятся в пространстве имен Contracts. Интерфейс, по сути, это соглашение между двумя классами, что один из них будет вести себя определенным образом. Это соглашение напоминает контракт между классами. И то и другое представляет собой одно и то же — соглашение, что класс предоставит определенные методы с конкретной сигнатурой.

Контракт Authenticatable требует наличия методов (таких как getAuthIdentifier()), которые позволяли бы фреймворку аутентифицировать экземпляры данной модели в системе аутентификации. Трейт Authenticatable включает в себя методы, обеспечивающие выполнение этого контракта для типичной модели Eloquent.

namespace Illuminate\Auth;

trait Authenticatable {
    protected $rememberTokenName = 'remember_token';

    public function getAuthIdentifierName() {
        return $this->getKeyName();
    }

    public function getAuthIdentifier() {
        return $this->{$this->getAuthIdentifierName()};
    }

    public function getAuthPassword() {
        return $this->password;
    }

    public function getRememberToken() {
        if (! empty($this->getRememberTokenName())) {
            return (string) $this->{$this->getRememberTokenName()};
        }
    }

    public function setRememberToken($value) {
        if (! empty($this->getRememberTokenName())) {
            $this->{$this->getRememberTokenName()} = $value;
        }
    }

    public function getRememberTokenName() {
        return $this->rememberTokenName;
    }
}

Контракту Authorizable нужен метод типа can(), который позволял бы фреймворку авторизовать экземпляры данной модели с предоставлением им соответствующих прав доступа в разных контекстах. Трейт Authorizable предоставляет методы, обеспечивающие выполнение контракта Authorizable для типичной модели Eloquent.

namespace Illuminate\Foundation\Auth\Access;

use Illuminate\Contracts\Auth\Access\Gate;

trait Authorizable {
    public function can($abilities, $arguments = [])
    {
        return app(Gate::class)->forUser($this)->check($abilities, $arguments);
    }

    public function cant($abilities, $arguments = []) {
        return ! $this->can($abilities, $arguments);
    }

    public function cannot($abilities, $arguments = []) {
        return $this->cant($abilities, $arguments);
    }
}

Наконец, контракт CanResetPassword требует методов вроде getEmailForPasswordReset() и sendPasswordResetNotification(), которые позволяли бы фреймворку сбрасывать пароль любой сущности, удовлетворяющей данному контракту. Трейт CanResetPassword предоставляет методы, обеспечивающие выполнение этого контракта для типичной модели Eloquent.

namespace Illuminate\Auth\Passwords;

use Illuminate\Auth\Notifications\ResetPassword as ResetPasswordNotification;

trait CanResetPassword {
    public function getEmailForPasswordReset() {
        return $this->email;
    }

    public function sendPasswordResetNotification($token) {
        $this->notify(new ResetPasswordNotification($token));
    }
}

Поиск: Laravel • Web-разработка • Класс • Пользователь • Форма • Фреймворк • Шаблон сайта • Аутентификация

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.