Banx. Создание страницы авторизации и сброса пароля

Aleksandr Serenko
F.A.F.N.U.R
Published in
5 min readSep 25, 2021

--

В данной статье приведем пример создания и использования 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

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock