Skip to main content
Менеджер финансов - Логин и регистрация

Менеджер финансов на Laravel и VueJS — пишем логин и регистрацию.

Часто новички изучающие Laravel и VueJS в качестве frontend фреймворка, задаются вопросами:

  • Как сделать ajax запрос?
  • Как зарегистрировать пользователя?
  • Как залогинить пользователя?

Сегодня мы напишем простую систему регистрации и логина для нашего приложения и на небольшом примере найдем ответы на вопросы.

В предыдущей статье мы настроили окружение и установили базовые компоненты, Laravel и Vue. Сейчас будем заниматься страницей регистрации и логина.

Шаг №1. Подготовка.

Мы не будем использовать пакеты для начального создания регистрации/логина, такие как laravel/ui, Jetstream или laravel/breeze.

Эти пакеты дают нам ненужный набор компонентов, которые мы использовать не будем. Поэтому регистрацию и логин напишем с чистого листа. Таким образом еще и научимся создавать свою «кастомную» регистрацию/логин.

Если вы используете docker в качестве среды разработки, все команды(кроме npm) нужно выполнять внутри контейнера. Для того чтобы попасть в терминал контейнера выполните команду docker exec -it finances-php bash

Нам нужно создать два контроллера, один для регистрации, второй для логина.

php artisan make:controller Auth/LoginController
php artisan make:controller Auth/RegistrationController

Теперь добавим маршруты в файл routes/api.php.

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::post('login', [\App\Http\Controllers\Auth\LoginController::class, 'login']);
Route::post('registration', [\App\Http\Controllers\Auth\RegistrationController::class, 'registration']);

Важно!

Этим мы только создали файлы контроллеров и прописали в маршрутах обработчики. Код сейчас работать не будет, работа с методами контроллеров находится в Шаге 4 и Шаге 5

Шаг №2. Основной контроллер, шаблон, настройка маршрутов.

Шаблоны нам не нужны, мы будем использовать vue в качестве фронтенд фреймворка, поэтому можем смело удалить все файлы в папке resources/views и оставим только один, home.blade.php.

resources/views/home.blade.php:

<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="{{mix('css/app.css')}}">
</head>
<body>
    <div id="app"></div>
    <script src="{{mix('js/app.js')}}"></script>
</body>
</html>

routes/web.php

<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/{any}', [App\Http\Controllers\HomeController::class, 'index'])
    ->where('any', '^(?!api).*$');

Мы будем использовать для роутинга vue-router, другими словами, весь клиентский роутинг будет на стороне vue(браузера). Поэтому нам нужна конструкция, которая все маршруты будет отправлять в HomeController, кроме маршрутов /api.

app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;

/**
 * Class HomeController
 * @package App\Http\Controllers
 */
class HomeController extends Controller
{
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Contracts\Support\Renderable
     */
    public function index()
    {
        return view('home');
    }
}

Шаг №3. Vue роутинг, шаблон страницы логина/регистрации.

Создадим файл в котором будем отображать все компоненты(страницы) нашего приложения, выполнять роутинг и прочие базовые вещи, нужные для работы фронта.

resources/js/api/auth.js

export const API_LOGIN_URL = '/api/login';
export const API_REGISTRATION_URL = '/api/registration';

В файлах из директории resources/js/api мы будем хранить константы с нашими api маршрутами.

resources/js/pages/Login.vue

<template>
    <div>
        <span v-if="loggedIn">Successfully logged in</span><br>
        <input type="text" v-model="form.email" placeholder="Email"><br>
        <input type="password" v-model="form.password" placeholder="Password"><br>
        <button @click="sendForm" :disabled="pending">Login</button>
    </div>
</template>

<script>
import axios from 'axios';
import { API_LOGIN_URL } from '../api/auth';

export default {
    name: 'Login',
    data() {
        return {
            pending: false,
            loggedIn: false,
            form: {
                email: null,
                password: null
            }
        }
    },
    methods: {
        sendForm() {
            if (this.pending === false) {
                this.pending = true;
                axios.post(API_LOGIN_URL, this.form)
                    .then(response => {
                        this.loggedIn = true;
                    })
                    .catch(errors => {})
                    .then(() => {
                        this.pending = false;
                    });
            }
        }
    }
}
</script>

<style scoped>

</style>

Это очень примитивный пример, для того чтобы показать примерную логику работы. В дальнейшем мы стилизуем этот компонент, добавим валидацию и редирект при успешной авторизации.

Здесь у нас для выполнения AJAX запроса к нашему backend приложению мы используем библиотеку axios. pending мы используем для того чтобы при множественных нажатиях на кнопку не слать много запросов пока предыдущий еще не завершится. Аналогично будет и на странице регистрации.

resources/js/pages/Registration.vue

<template>
    <div>
        <span v-if="registered">Successfully registered</span><br>
        <input type="text" v-model="form.name" placeholder="Name"><br>
        <input type="text" v-model="form.email" placeholder="Email"><br>
        <input type="password" v-model="form.password" placeholder="Password"><br>
        <input type="password" v-model="form.password_confirmation" placeholder="Password Confirmation"><br>
        <button @click="sendForm" :disabled="pending">Registration</button>
    </div>
</template>

<script>
import axios from 'axios';
import {API_REGISTRATION_URL} from '../api/auth';

export default {
    name: 'Registration',
    data() {
        return {
            pending: false,
            registered: false,
            form: {
                name: null,
                email: null,
                password: null,
                password_confirmation: null,
            }
        }
    },
    methods: {
        sendForm() {
            if (this.pending === false) {
                this.pending = true;
                axios.post(API_REGISTRATION_URL, this.form)
                    .then(response => {
                        this.registered = true;
                    })
                    .catch(errors => {})
                    .then(() => {
                        this.pending = false;
                    });
            }
        }
    }
}
</script>

Для регистрации сделали тоже довольно примитивный компонент в котором будет только отправка формы.

Теперь нам нужно собрать всё в единое целое и чтобы работало. Начинаем подключать маршрутизатор.

Для того чтобы установить vue-router, нужно выполнить команду npm install vue-router. (Полное руководство https://router.vuejs.org/ru/installation.html).

Создадим файл resources/js/router/index.js, в нем будем описывать маршруты приложения.

resources/js/router/index.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import Login from '../pages/Login';
import Registration from '../pages/Registration';

Vue.use(VueRouter);

const routes = [
    {
        path: '/',
    },
    {
        path: '/login',
        component: Login
    },
    {
        path: '/registration',
        component: Registration
    }
];

const router = new VueRouter({
    mode: 'history',
    routes
});

export default router;

resources/js/components/App.vue

<template>
    <div>
        <router-link to="/login">Login</router-link>
        <router-link to="/registration">Registration</router-link>
        <router-view></router-view>
    </div>
</template>

<script>
export default {
    name: 'App'
}
</script>

<style scoped>

</style>

Теперь все готово, осталось только отредактировать файл resources/js/app.js и можно запускать билд нашего приложения.

/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

import App from './components/App';
import router from './router';

window.Vue = require('vue');

/**
 * The following block of code may be used to automatically register your
 * Vue components. It will recursively scan this directory for the Vue
 * components and automatically register them with their "basename".
 *
 * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
 */

// const files = require.context('./', true, /\.vue$/i)
// files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default))

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

const app = new Vue({
    el: '#app',
    router,
    render: h => h(App)
});

Для «компиляции» vue в понятный браузеру js у нас уже есть несколько команд:

  • npm run dev — быстрый билд без минификаций и оптимизаций
  • npm run prod — готовый для продакшена билд
  • npm run watch — запущен постоянно и при изменении файлов, автоматически их билдит, удобно при разработке

Для локальной разработки нам достаточно держать постоянно запущенным npm run watch.

Если вы все сделали правильно, открыв страницу http://localhost, вы должны увидеть слева вверху 2 ссылки на логин и регистрацию, а по клике по ним 2 разные формы.

Шаг №4. Регистрация

Первым делом создадим класс (ссылка на документацию) для валидации входящих данных при регистрации, делается это командой:

 php artisan make:request RegisterUserRequest

Откроем файл, который был создан командой выше app/Http/Requests/RegisterUserRequest.php и добавим правила валидации (не забудьте удалить метод authorize).

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

/**
 * Class RegisterUserRequest
 * @package App\Http\Requests
 */
class RegisterUserRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required|confirmed',
        ];
    }

Добавили правила для полей name, email, password(список всех правил).

Редактируем контроллер

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterUserRequest;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;

/**
 * Class RegistrationController
 * @package App\Http\Controllers\Auth
 */
class RegistrationController extends Controller
{
    /**
     * @param RegisterUserRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function registration(RegisterUserRequest $request)
    {
        try {
            User::create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => Hash::make($request->password)
            ]);
            return response()->json([], Response::HTTP_OK);
        } catch (\Exception $e) {
            Log::error($e->getMessage(), ['trace' => $e->getTraceAsString()]);
            return response()->json([], Response::HTTP_INTERNAL_SERVER_ERROR);
        }
    }
}

Прописываем аргументом метода registration наш новый класс RegisterUserRequest.

Далее мы формируем массив с данными для передачи их в метод создания модели. При формировании массива, для пароля генерируем хеш фасадом Hash, метод make.

На данном этапе кода контроллера будет достаточно, даже с пустым ответом, мы будем ориентироваться на код ответа, а не на тело.

Шаг №5. Аутентификация (Логин)

Поскольку мы используем SPA и REST api, использовать механизм сессий не получится. Для того, чтобы backend(laravel) мог понять какой пользователь залогене, есть простой в использовании пакет laravel/sanctum (Документация).

Устанавливаем пакет командой:

composer require laravel/sanctum

После установки, добавляем в проект файлы конфигурации:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

Для подключения Sanctum к проекту, нужно добавить middleware ко всем маршрутам api. Откроем файл app/Http/Kernel.php. В файле находим свойство protected $middlewareGroups и к массиву с ключом api добавляем в самое начало строку \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class.

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

Последним и завершающим шагом в предварительной настройке пакета, это добавление трейта для модели пользователя.

Откроем файл app/Models/User.php и добавим трейт HasApiTokens:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

/**
 * Class User
 * @package App\Models
 */
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

Не забудьте добавить в начале файла после объявления namespaceuse Laravel\Sanctum\HasApiTokens;

Создадим FormRequest для запроса на логин, чтобы провалидировать входящие данные:

php artisan make:request LoginUserRequest

app/Http/Requests/LoginUserRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

/**
 * Class LoginUserRequest
 * @package App\Http\Requests
 */
class LoginUserRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => 'required|email',
            'password' => 'required',
        ];
    }
}

app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\LoginUserRequest;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;

/**
 * Class LoginController
 * @package App\Http\Controllers\Auth
 */
class LoginController extends Controller
{
    /**
     * @param LoginUserRequest $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function login(LoginUserRequest $request)
    {
        if (Auth::attempt($request->validated())) {
            $token = Auth::user()->createToken('api');
            return response()->json([
                'token' => $token->plainTextToken
            ]);
        }

        return response()->json([], Response::HTTP_UNAUTHORIZED);
    }
}

  • Auth::attempt — метод попробует залогинить пользователя. Сходит в базу, поищет там пользователя, сравнит пароли и выдаст результат, успешно или нет.
  • $request->validated() — передаст только те поля, которые прописаны в правилах валидации.
  • Auth::user() — возвращает нам объект залогиненого пользователя.
  • createToken('api') — сгенерирует объект токена для пользователя. Аргументом мы передаем имя токена, может быть на ваше усмотрение, полезно когда пользователь заходит на сайт с разных источников, например с веб интерфейса, мобильного приложения или через десктопное.
  • $token->plainTextToken — берет строковое значение токена.

Шаг №6. Запускаем приложение.

  • Проверяем есть ли в корневой директории файл .env (если нет, создаем и копируем все из файла примера — .env.example)
  • Проверяем значение APP_KEY(если пустое, запускаем команду php artisan key:generate)
  • Добавляем логин, пароль и имя базы данных для подключения к базе данных. (В предыдущем уроке я использовал для окружения докер, напишу конфигурацию под него)
    • DB_HOSTdatabase (Поскольку я запускаю в докере, в качестве хоста нужно писать имя сервиса из doker-compose.yml файла)
    • DB_DATABASEfinances
    • DB_USERNAMEroot
    • DB_PASSWORDsecret
  • Запускаем миграции командой php artisan migrate
  • Запускаем сборку фронта командой npm run dev

После подготовительных этапов открываем сайт (у меня это адрес http://localhost), переходим на страницу регистрации, заполняем поля и жмем кнопку Registration. Если вы все сделали правильно, у вас должна появится такая страница с сообщением об успешной регистрации:

Переходим на страницу Login и вводим данные которые использовали при регистрации. Если увидели сообщение об успешном логине, значит все работает.

Довольно большая статья получилась, надеюсь я все правильно и доступно написал. В следующей статье мы с вами найдем и добавим шаблон для главной страницы/регистрации/логина, хранилище VueX.

Если у вас появились вопросы, вы заметили неточности или у вас появились ошибки, пишите комментарии будем разбираться.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *