Части
перейти к части...
Система аутентификации - важная часть одностраничного приложения. Однако, прежде чем мы начнем, давай отредактируем метод API\AuthController@login
из первой части урока. Я осознал, что сейчас он сделан немного неграмотно. Наверняка нам понадобится информация о только что авторизированном пользователе, а не только токены. Ниже отредактированный код. Я добавил комментарии, так что дополнительные пояснения излишни.
public function login()
{
// Проверяем существует ли пользователь с указанным email адресом
$user = User::whereEmail(request('username'))->first();
if (!$user) {
return response()->json([
'message' => 'Wrong email or password',
'status' => 422
], 422);
}
// Если пользователь с таким email адресом найден - проверим совпадает
// ли его пароль с указанным
if (!Hash::check(request('password'), $user->password)) {
return response()->json([
'message' => 'Wrong email or password',
'status' => 422
], 422);
}
// Внутренний API запрос для получения токенов
$client = DB::table('oauth_clients')
->where('password_client', true)
->first();
// Убедимся, что Password Client существует в БД (т.е. Laravel Passport
// установлен правильно)
if (!$client) {
return response()->json([
'message' => 'Laravel Passport is not setup properly.',
'status' => 500
], 500);
}
$data = [
'grant_type' => 'password',
'client_id' => $client->id,
'client_secret' => $client->secret,
'username' => request('username'),
'password' => request('password'),
];
$request = Request::create('/oauth/token', 'POST', $data);
$response = app()->handle($request);
// Проверяем был ли внутренний запрос успешным
if ($response->getStatusCode() != 200) {
return response()->json([
'message' => 'Wrong email or password',
'status' => 422
], 422);
}
// Вытаскиваем данные из ответа
$data = json_decode($response->getContent());
// Формируем окончательный ответ в нужном формате
return response()->json([
'token' => $data->access_token,
'user' => $user,
'status' => 200
]);
}
Теперь давайте займемся страницей входа. Отредактируйте Login.vue
следующим образом:
<template>
<div>
<h1>Login</h1>
<p>
<label for="username">Email</label>
<input type="text" name="username" v-model="username">
</p>
<p>
<label for="password">Password</label>
<input type="password" name="password" v-model="password">
</p>
<button @click="login">Login</button>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
};
},
methods: {
login() {
let data = {
username: this.username,
password: this.password
};
axios.post('/api/login', data)
.then(({data}) => {
// TODO: сохранить полученные данные
// data.token
// data.user
})
.catch(({response}) => {
alert(response.data.message);
});
}
}
}
</script>
Это простая форма, которая отсылает AJAX запрос с помощью axios
(который установлен в Laravel по-умолчанию) на наш маршрут /api/login
. Базовых знаний Vue.js будет достаточно для того, чтобы понять этот код.
Итак, мы получаем токен и информацию о пользователе в ответе. Нам нужно сохранить эти данные так, чтобы они не потерялись после перезагрузки страницы или если пользователь закроет браузер. Мы будем хранить данные в локальном хранилище HTML5. Этот метод хранения уязвимыми к XSS атакам, а чтобы предотвратить XSS атаки, нужно все время перепроверять данные в backend'е. Есть и другой метод, но и он не на 100% безопасен. Подробнее о проблеме можно почитать в этой статье (на английском).
Давайте создадим класс Auth
на ES6, который будет отвечать за вопросы, связанные с аутентификацией. Что-то вроде класса Auth в Laravel. Создайте файл resources/assets/js/auth.js
, который будет синглтоном.
class Auth {
constructor() {
}
}
export default new Auth();
Сделайте объект класса доступным глобально, добавив следующий код вверху файла resources/assets/js/app.js
:
import auth from './auth.js';
window.auth = auth;
Дальше, давайте откроем auth.js
и создадим метод login
, который просто будет сохранять полученные данные.
class Auth {
constructor() {
}
login (token, user) {
window.localStorage.setItem('token', token);
window.localStorage.setItem('user', JSON.stringify(user));
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}
}
Работать с локальным хранилищем HTML5 очень просто. Это простое хранилище пар ключ-значение, где значения могут быть только строковыми. Так как user
это объект, нам нужно привести его в строковый формат перед сохранением. После сохранения данных мы задаем заголовок Authorization
в axios, чтобы токен был включен в запрос при последующих AJAX запросах.
Наконец, нам нужно вызвать новый метод в файле Login.vue
сразу после успешного запроса к API. Также было бы логичным перенаправить пользователя в зону приложения для авторизированных пользователей, в нашем случае это страница по адресу /dashboard
. Редирект выполняется с помощью метода push
маршрутизатора, доступ же к маршрутизатору можно получить из любого Vue файла через this.$router
.
axios.post('/api/login', data)
.then(({data}) => {
auth.login(data.token, data.user);
this.$router.push('/dashboard');
})
.catch(({response}) => {
alert(response.data.message);
});
Отлично, теперь нам нужно обновить пользовательский интерфейс, чтобы отобразить, что пользователь вошел в систему. Как минимум нам нужно заменить ссылку Login
на Logout
и, возможно, показать имя пользователя где-нибудь. Наш объект auth
служит нам глобальным хранилищем, но, к сожалению, Vue не может отслеживать изменения в нем. Чтобы Vue был в курсе изменений, нам придется создать глобальный менеджер событий, который будет просто еще одним объектом Vue. Сделаем это в файле app.js
.
Vue.use(VueRouter);
window.Event = new Vue;
Теперь нам нужно добавить кое-что в класс auth.js
. Для начала добавим 2 свойства для хранения данных. Отредактируйте конструктор следующим образом:
constructor () {
this.token = null;
this.user = null;
}
Мы будем задавать эти значения в конце метода login
. После, вызовем глобальное событие.
...
login (token, user) {
window.localStorage.setItem('token', token);
window.localStorage.setItem('user', JSON.stringify(user));
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
this.token = token;
this.user = user;
Event.$emit('userLoggedIn');
}
Наконец, давайте добавим вспомогательный метод, который будет проверять авторизирован ли пользователь в данный момент времени. В методе мы просто будем проверять, задано ли значение у свойства this.token
.
check () {
return !! this.token;
}
Кстати, вы наверняка подумали, что наша система не безопасна - любой может выполнить auth.login('token', {});
в консоли браузера и интерфейс нашего приложения изменится на интерфейс авторизированного пользователя, дав доступ к страницам, которые без авторизации не должны быть доступны. Отчасти это так, но помните, что наш сервер вернет 401
после первого же AJAX запроса с подставным токеном, так что никаких реальных изменений на стороне backend'а не произойдет. Что же касается изменения в таком случае интерфейса обратно на интерфейс неавторизированного пользователя - мы займемся этим позже.
Итак, единственный файл, который остается отредактировать - это Layout.vue
. Сначала отредактируйте блок <script>
:
<script>
export default {
data() {
return {
authenticated: auth.check(),
user: auth.user
};
},
mounted() {
Event.$on('userLoggedIn', () => {
this.authenticated = true;
this.user = auth.user;
});
},
}
</script>
Мы считываем изначалный статус аутентификации при загрузке страницы. Затем, в методе mounted()
мы добавляем слушатель для глобального события userLoggedIn
и заного считываем данные из глобально объекта auth
, когда событие срабатывает. С этим должно быть понятно, дальше замените ссылку Login
на вот это:
<div v-if="authenticated && user">
<p>Hello, {{ user.name }}</p>
<router-link to="/logout">Logout</router-link>
</div>
<router-link to="/login" v-else>Login</router-link>
Если свойство authenticated
равно true
и объект user
не null
и не undefined
- показываем блок с именем пользователя и ссылкой Logout
. Иначе, показываем ссылку Login
без имени пользователя.
У нас осталось еще несколько моментов, о которых нужно позаботиться. Но для начала, почему бы вам не сделать функционал выхода из системы самостоятельно в качестве домашней работы? По большому счету достаточно просто сделать шаги из метода login()
в обратном порядке. Просто очистите все данные, задав переменным пустые строки или null, и перенаправьте пользователя на главную страницу (home). Увидимся в следующей части!
Все материалы на сайте voerro абсолютно бесплатны и написаны автором в свободное от основной работы время. Если уроки сайта оказались для вас полезными, пожалуйста, помогите проекту. Спасибо!