Banx. Создание страницы авторизации и сброса пароля
В данной статье приведем пример создания и использования shared компонентов для страниц входа и сброса пароля.
Реализуем две страницы в проекте:
- Страницу входа (login)
- Страница сброса пароля (recovery)
Для аутентификации пользователя используем JWT. Так как в качестве backend’а используется nestjs. Добавим в него немного логики для генерации JWT по логину и паролю.
Решение построено на документации NestJs — Authentication.
В результате получится 2 endpoint’а:
### Login
POST http://localhost:3000/auth/login
Content-Type: application/json
Accept: application/json
{
"phone": "9230000000",
"password": "123456"
}
### Recovery
POST http://localhost:3000/auth/recovery
Content-Type: application/json
Accept: application/json
{
"phone": "9230000000",
"birthdate": "1992-02-02"
}
Создание страницы авторизации
Если присмотреться к демо, то можно увидеть, что страницы авторизации и сброса пароля очень похожи. Каждая из страниц имеет заголовок, подзаголовок, форму и подсказку. Отличаются страницы лишь элементами формы, часть из которых совпадает.
И если заголовки и подзаголовки можно переиспользовать, то делать общие элементы формы зачастую не стоит, так как обычно в форме меняются поля (названия, добавляется логика и валидация, которая может вести себя по разному на разных страницах).
Создадим следующую структуру:
banx/
├── apps/
│ └── russian/
│ └── cabinet/
├── libs/
│ ├── auth/
│ │ ├── api/
│ │ ├── guards/
│ │ ├── login/
│ │ │ └── page/
│ │ ├── pages/
│ │ ├── recovery/
│ │ │ └── page/
│ │ ├── shared/
│ │ ├── state/
Каждая из Nx библиотек будет отвечать за свою часть логики:
api
— сервис, который будет отправлять данные для авторизации/сброса пароля;guards
— будет содержать guard, который не будет пускать авторизованных клиентов на страницы входа/сброса;login/page
— страница с формой авторизации;pages
— модуль, который подключает лениво все страницы auth и добавляет соответствующие гуарды;recovery/page
— страница сброса пароля;shared
— модуль общих компонентов, которые будут использоваться на страницах входа/сброса пароля;state
— модуль, который хранит стейт и фасад, для вызова соответствующих экшенов.
Auth API Service
Добавим модуль и сервис, который будет работать с 2 endpoint’ами, которые были представлены выше.
Как видно из реализации, сервис предоставляет два метода: login
и recovery
.
Стоит отметить, из-за того, что реализация backend’а и frontend’а происходит в одном монорепозитории, все абстракции были вынесены в модуль users/common, который содержит все абстракции, модели и интерфейсы для пользователя.
Auth State
Добавим простую реализацию для входа/сброса пароля:
Auth state содержит следующее:
- Хранит в себе только одно значение —
logged
, признак клиента, который является авторизованным или нет; - При инициализации state’а, восстанавливаем значения
logged
, на основании информации из session storage (проверяем есть ли JWT токен). - При logout’е удаляется
JWT
токен - Экшены содержат две цепочки событий
login
иrecovery
, которые позволяют авторизоваться и сбросить пароль соответственно.
Auth guards
Добавим логику проверки доступа к страницам авторизации и сброса пароля:
Гуадр смотрит на наличие JWT токена и пускает или либо не пускает текущего пользователя на страницу.
Auth Shared
На демо видно два типа компонент:
- Компоненты страницы (карточки), которые включают в себя: заголовок, подзаголовок, подсказку снизу;
- Компоненты формы: форма, ошибка при неверных данных.
Следуя данному разделению, создадим два модуля: AuthCardModule
и AuthFormModule
.
Модуль карточки будет содержать:
AuthCardHintModule
— подсказка для пользователяAuthCardLinksModule
— ссылки на смену типа авторизацииAuthCardSubtitleModule
— подзаголовокAuthCardTitleModule
— заголовок
Так как большинство компонент лишь содержать только CSS стили, не будем вдаваться в детальный обзор.
Единственное, что здесь заслуживает внимания это принцип экспортирования общих компонентов:
@NgModule({
imports: [CommonModule, RouterModule, AuthCardTitleModule, AuthCardSubtitleModule, AuthCardHintModule, AuthCardLinksModule],
declarations: [AuthCardComponent],
exports: [AuthCardComponent, AuthCardTitleComponent, AuthCardSubtitleComponent, AuthCardHintComponent, AuthCardLinksComponent],
})
export class AuthCardModule {}
Правда полезность этого не понятна, так как нужно посмотреть, что будет экспортировать Angular.
Аналогично создадим несколько общих компонентов для формы:
Все выше представленные компоненты добавляют лишь стилистическое оформление. Но для того, чтобы не дублировать стили, все компоненты, которые используются на обоих страницах, были вынесены в shared.
Login page
Создадим модуль и компонент:
И добавим компонент формы авторизации:
Немного из внутренностей страницы авторизации.
Вся логика с авторизацией вынесена из компонента LoginPageComponent
в LoginFormModule
. Это сделано потому что страница авторизации может меняться кардинально (возможны изменения текстов, дизайна, добавление промо информации), а вот логика формы останется скорее всего неизменной (необходимо ввести логин и пароль и нажать на кнопку).
Компонент формы (LoginFormComponent
) реализует всю логику, связанную с авторизацией. Сначала идет инициализация формы:
this.form = new FormGroup({
[UserField.Phone]: new FormControl(null, [Validators.required]),
[UserField.Password]: new FormControl(null, [Validators.required]),
});
Затем идет подписка на событие изменения формы, и если данные в форме меняются, то убирается сообщение об ошибке.
this.form.valueChanges
.pipe(
tap(() => {
if (this.loginError) {
this.loginError = null;
this.changeDetectorRef.markForCheck();
}
}),
takeUntil(this.destroy$)
)
.subscribe();
С помощью AuthFacade
идет подписка на два события: успешной и не успешной авторизации.
this.authFacade.loginFailure$
.pipe(
tap((loginError) => {
this.loginError = loginError;
this.changeDetectorRef.markForCheck();
}),
takeUntil(this.destroy$)
)
.subscribe();
this.authFacade.loginSuccess$
.pipe(
tap(() => void this.navigationService.navigateByUrl(this.paths.home)),
takeUntil(this.destroy$)
)
.subscribe();
При успешной авторизации, клиент направится на главную страницу, а в случае ошибки, пользователю будет показана ошибка.
Recovery Page
Страница сброса пароля почти идентична странице входа. Различия между страницами это одно поле и вызов метода в фасаде.
Главное отличие формы сброса пароля:
onReset(): void {
this.form?.markAllAsTouched();
if (this.form?.valid) {
this.authFacade.recovery(this.getRecovery());
}
this.changeDetectorRef.markForCheck();
}
И если пароль был успешно сброшен, то тогда показывается popup:
this.authFacade.recoverySuccess$
.pipe(
tap(() => {
void this.matDialog.open(RecoverySuccessDialogComponent, { data: this.getRecovery().phone });
void this.navigationService.navigateByUrl(this.paths.authLogin);
}),
takeUntil(this.destroy$)
)
.subscribe();
Резюме
В данной статье привели реализацию двух страниц: авторизации и сброса пароля. На примере двух похожих страниц, создали shared компоненты, которые потом использовали на обоих страницах.
Ссылки
Вернуться к оглавлению — Введение.
Предыдущая статья — Banx. Гибкие шаблоны с routerOutlet в Angular.
Следующая статья — Banx. Создание трекера событий пользователя в Angular.
Все исходники находятся на github, в репозитории:
Для того, чтобы посмотреть состояние проекта на момент написания статьи, нужно выбрать соответствующий тег — auth.
git checkout auth
Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular, веб-разработку и новости из мира фронтенда.
Группа в Medium: https://medium.com/fafnur
Группа в Vkontakte: https://vk.com/fafnur
Группа в Facebook: https://www.facebook.com/groups/fafnur/
Telegram канал: https://t.me/f_a_f_n_u_r
Twitter: https://twitter.com/Fafnur1
Instagram: https://www.instagram.com/fafnur
LinkedIn: https://www.linkedin.com/in/fafnur