Авторизация - TupotaValentyn/MA.-Project GitHub Wiki
Для удобства пользователей предлагаю ввести несколько способов авторизации: обычная (почта + пароль) и авторизация
через социальные сети. Из сетей предлагаю взять наиболее популярны и простые в разработке: Google и Facebook. Они дают
возможность по API получить имя юзера и его почту для возможности отсылать электронные письма.
Так как на фронт-енде будет SPA, добавить поддержку обычных сессий не получится. Поэтому идентификация пользователя будет производиться посредством специального токена, который будет передан на front-end при успешной авторизации. На фронте нужно будет его сохранить куда-то для возможности дальнейшего использования, например, в localStorage и передавать его в хедерах при каждом запросе к апи. При использовании axios это удобно сделать при помощи interceptors:
const instance = axios.create({
baseURL: 'http://localhost:3000',
});
instance.interceptors.request.use(config => {
config.headers.authorization = `Bearer ${localStorage.getItem('token')}`;
return config;
});
На сервере этот ключ будет проверяться на валидность и если с ним все ок, фронт получит запрашиваемые данные.
Если токен невалиден, в ответе придет HTTP статус 403. Если его срок действия истек, в ответе придет HTTP статус 401. В таких случаях нужно удалить старый токен из localStorage и отправить пользователя на страницу входа на сайт для повторной авторизации.
При использовании axios это также удобно сделать при помощи interceptors:
instance.interceptors.response.use(
response => response,
error => {
if (403 === error.response.status) {
localStorage.removeItem('token');
router.replace({ name: 'login' });
} else {
return Promise.reject(error);
}
},
);
Ну и в конце нужно повесить guard на роутер:
router.beforeEach((to, from, next) => {
const isAuthorized = authService.isAuthorized();
if (['login', 'register'].includes(to.name) && isAuthorized) {
return next({ name: 'home' });
}
if (to.meta.requiresAuth) {
if (isAuthorized) {
next();
} else {
next({ name: 'login' });
}
} else {
next();
}
});
Ссылка на документацию - http://localhost:3000/api-docs/#/Auth/post_auth_register
Отправляем на сервер почту + пароль. В БД будет добавлен новый пользователь, но его статус будет "Не подтвержден" (флаг isConfirmed: будет false). Вместе с этим на почту будет выслано письмо с ссылкой для подтверждения почты.
Ответом на этот запрос фронт получит уникальный хеш-строку пользователя { userHash: 'blah-blah-blah' }
. В нем закодирована информация о пользователе и он служит для дальнейшей идентификации юзера. Эти данные можно сохранить в localStorage для дальнейшего использования (при повторном входе на сайт если эти данные есть, сразу кидать на страничку оповещения о неподтвержденном email (см. дальше)).
На фронте нужно будет перебросить пользователя на страничку, где сказано, что на почту отправлено письмо для подтверждения + 2 кнопки: "Выслать повторно" и "Проверить подтверждение".
Клик на кнопку "Выслать повторно" отправляет запрос на /auth/resend_confirmation/{userHash}
(документация - http://localhost:3000/api-docs/#/Auth/post_auth_resend_confirmation__userHash_)
Клик на кнопку "Проверить подтверждение" - запрос на /auth/check_verification/{userHash}
(документация - http://localhost:3000/api-docs/#/Auth/get_auth_check_verification__userHash_). Если почта подтверждена, юзер может полноценно использовать систему.
Ссылка на документацию - http://localhost:3000/api-docs/#/Auth/post_auth_login
Отправляем на сервер почту + пароль. Если аккаунт пользователя не подтвержден, ответом на этот запрос фронт получит уникальный хеш-строку пользователя { userHash: 'blah-blah-blah' }
. В таком случае нужно перенаправить пользователя на страницу оповещения о неподтвержденном email. Flow действий на этой странице см. выше в разделе "Регистрация"
Если почта уже подтверждена, фронт получит данные о токене авторизации вида { tokenData: TokenData }
. В этом случае необходимо сохранить эти данные на фронте и перенаправить пользователя в систему.
Ссылка на документацию - http://localhost:3000/api-docs/#/Auth/post_auth_google
Для получения возможности входа через Google необходимо создать проект и получить API ключ.
На сайт нужно подключить Google Auth SDK:
<script src="https://apis.google.com/js/platform.js" async defer></script>
После инициализации SDK можно повесить обработчик на клик на кнопку входа через Google:
window.gapi.load('auth2', () => {
const instance = window.gapi.auth2.init({
client_id: process.env.GOOGLE_CLIENT_ID,
});
instance.attachClickHandler(
// Элемент, на который вешаем клик
document.querySelector('.js-login-with-google'),
// Объект опций (https://cutt.ly/qtfoSOy)
{},
// Callback, который выполнится после успешной авторизации пользователя. Объект
// googleUser содержит всю запрашиваемую инфу о юзере
googleUser => {},
// Callback, который выполнится после неудачной авторизации пользователя
error => {},
);
});
В обработчике успешного входа нужно получить id_token пользователя и отправить его на сервер:
async googleUser => {
try {
const idToken = googleUser.getAuthResponse().id_token;
const response = await axios.post('/login/google', { idToken });
const { tokenData } = response.data;
// Сохраняем токен и публичные данные о пользователе в localStorage, например
// ...
this.$router.replace({ name: 'home' });
} catch (error) {
this.authError = error.message;
}
}
Если в базе уже есть пользователь с таким же email, как в профиле Google, аккаунт Google будет привязан к этому пользователю.
В ответ фронт получит данные о токене { tokenData: TokenData }
. После этого юзер может полноценно использовать систему.
См. доку чтобы получить больше инфы
Ссылка на документацию - http://localhost:3000/api-docs/#/Auth/post_auth_facebook
Для получения возможности входа через Facebook необходимо создать проект и получить API ключ.
На сайт нужно подключить Facebook Auth SDK:
<script src="https://connect.facebook.net/en_US/sdk.js" async defer></script>
После загрузки скрипта нужно инициализировать модуль авторизации:
window.FB.init({
appId: process.env.FACEBOOK_CLIENT_ID,
cookie: true,
xfbml: true,
version: 'v6.0',
});
Далее можем навесить обработчик на кнопку входа через Facebook. В нем нужно получить токен пользователя и его ID и отправить эти данные на сервер:
window.FB.login(async response => {
const accessToken = response.authResponse.accessToken;
const userId = response.authResponse.userID;
const res = await axios.post('/login/facebook', { accessToken, userId });
const { tokenData } = res.data;
// Сохраняем токен и публичные данные о пользователе в localStorage, например
// ...
this.$router.replace({ name: 'home' });
});
Если в базе уже есть пользователь с таким же email, как в профиле Facebook, аккаунт Facebook будет привязан к этому пользователю.
В ответ фронт получит данные о токене { tokenData: TokenData }
. После этого юзер может полноценно использовать систему.
См. доку чтобы получить больше инфы
В модели UserPublicData есть поле authType, которое определяет, каким способом пользователь вошел на сайт. В зависимости от способа входа выход тоже будет разным.
Нужно всего лишь удалить данные о пользователе и токене из localStorage
Нужно вызвать метод .disconnect() и удалить данные о пользователе и токене из localStorage
window.gapi.load('auth2', () => {
const instance = window.gapi.auth2.init({
client_id: process.env.GOOGLE_CLIENT_ID,
});
instance.disconnect();
});
Нужно вызвать метод .logout() и удалить данные о пользователе и токене из localStorage
window.FB.logout(() => {
console.log('User is logged out');
})
Хранить токен в localStorage - небезопасно, поэтому есть несколько вариантов прохождения процесса авторизации:
- Забить на это и использовать jsonwebtoken как описано выше
- Заменить SPA на SSR. Это позволит ввести нормальные безопасные сессии через http-only cookies
- Заставить пользователя авторизироваться при каждом входе на сайт. После получения токена его можно сохранить в переменной внутри приложения, к которой не будет доступа у внешних скриптов.