Skip to content

Аутентификация

Система аутентификации в SaaS Admin основана на JWT токенах с поддержкой refresh tokens.

Обзор

Аутентификация включает в себя:

  • ✅ Вход по email и паролю
  • ✅ Регистрация новых пользователей
  • ✅ JWT access и refresh токены
  • ✅ Автоматическое добавление токена к запросам
  • ✅ Защита маршрутов через guards
  • ✅ Автоматический logout при 401

Архитектура

┌─────────────┐
│   User      │
└──────┬──────┘


┌─────────────────┐     ┌──────────────┐
│  Login Page     │────▶│ AuthService  │
└─────────────────┘     └──────┬───────┘


                        ┌─────────────┐
                        │  API        │
                        │  /admin/auth│
                        └──────┬──────┘


                      ┌─────────────────┐
                      │ StorageService  │
                      │ (localStorage)  │
                      │ - access_token  │
                      │ - refresh_token │
                      └─────────────────┘

Функциональность

Вход в систему

Страница: /auth/login

  1. Пользователь вводит email и пароль
  2. Отправка запроса POST /auth/sign-in
  3. Получение access и refresh токенов
  4. Сохранение токенов в localStorage
  5. Редирект на /admin/dashboard

Пример кода:

typescript
@Component({
	selector: "app-login",
	standalone: true,
	imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule],
	templateUrl: "./login.component.html"
})
export class LoginComponent {
	private readonly authService = inject(AuthService);
	private readonly router = inject(Router);

	loginForm = new FormGroup({
		email: new FormControl("", [Validators.required, Validators.email]),
		password: new FormControl("", [Validators.required])
	});

	onSubmit() {
		if (this.loginForm.valid) {
			const { email, password } = this.loginForm.value;

			this.authService.login(email!, password!).subscribe({
				next: () => {
					this.router.navigate(["/admin/dashboard"]);
				},
				error: (error) => {
					console.error("Login failed:", error);
				}
			});
		}
	}
}

Регистрация

Страница: /auth/register

  1. Пользователь заполняет форму регистрации
  2. Отправка запроса POST /auth/sign-up
  3. Автоматический вход после успешной регистрации

Выход из системы

Выход происходит при:

  • Нажатии кнопки "Выход"
  • Получении 401 ошибки
  • Истечении токена
typescript
logout() {
  this.authService.logout();
  // Токены удаляются
  // Редирект на /auth/login
}

AuthService API

AuthService використовує HttpClient напряму для auth ендпоінтів, ApiService для admin-операцій, StorageService для зберігання токенів, UniversalService для SSR-сумісності та EnvService для baseUrl.

Методы

login(email: string, password: string): Observable<undefined>

Виконує вхід користувача. Використовує HttpClient напряму.

typescript
this.authService.login("user@example.com", "password123").subscribe(() => {
	console.log("Успешный вход");
});

logout(): void

Виконує вихід, очищує токени через StorageService і робить редірект.

typescript
this.authService.logout();

me(): Observable<IUser>

Отримує інформацію про поточного користувача. Використовує HttpClient напряму.

typescript
this.authService.me().subscribe((user) => {
	console.log(user.email);
});

refreshTokens(): Observable<ILoginResponse>

Оновлює access та refresh токени. Використовує HttpClient напряму.

typescript
this.authService.refreshTokens().subscribe(() => {
	// Токени автоматично оновлюються
});

updateMe(data: { firstName?: string; lastName?: string }): Observable<IUser>

Оновлює профіль поточного користувача. Використовує ApiService.


getAdminUsers(params?): Observable<IPaginatedResponse<IUser>>

Отримує список адмін-користувачів з пагінацією. Використовує ApiService.


createAdminUser(data): Observable<IUser>

Створює нового адмін-користувача. Використовує ApiService.


deleteAdminUser(id: string)

Видаляє адмін-користувача. Використовує ApiService.


getTokenClaims(): JwtPayload | null

Декодує JWT токен та повертає claims.

typescript
const claims = this.authService.getTokenClaims();
// claims містить: sub, email, exp, iat і т.д.

isAuthenticated: boolean

Перевірка авторизації. Повертає false на сервері (SSR).

typescript
if (this.authService.isAuthenticated) {
	// Користувач авторизований
}

isAuthenticated$: Observable<boolean>

Observable для реактивної перевірки авторизації.

typescript
this.authService.isAuthenticated$.subscribe((isAuth) => {
	this.showAdminPanel = isAuth;
});

Guards

authGuard

Защищает маршруты, требующие авторизации.

Использование:

typescript
const routes: Routes = [
	{
		path: "admin",
		canActivate: [authGuard],
		loadComponent: () => import("./admin.component")
	}
];

Логика:

typescript
export const authGuard: CanActivateFn = () => {
	const authService = inject(AuthService);
	const router = inject(Router);

	if (!authService.isAuthenticated) {
		return router.createUrlTree(["/auth/login"]);
	}

	return true;
};

noAuthGuard

Редиректит авторизованных пользователей (для страниц login/register).

Использование:

typescript
const routes: Routes = [
	{
		path: "auth/login",
		canActivate: [noAuthGuard],
		loadComponent: () => import("./login.component")
	}
];

Interceptors

Auth Interceptor

Автоматически додає JWT токен до всіх запитів через заголовок x-admin-token, використовуючи StorageService (через AuthService.getAccessToken() з ключем STORAGE_KEYS.ACCESS_TOKEN). Також обробляє 401 помилки з автоматичним оновленням токенів.

typescript
const AUTH_URLS = ["/auth/sign-in", "/auth/refresh"];

function addToken(req: Parameters<HttpInterceptorFn>[0], token: string | null) {
	if (!token) return req;
	return req.clone({ setHeaders: { "x-admin-token": token } });
}

export const authInterceptor: HttpInterceptorFn = (req, next) => {
	const authService = inject(AuthService);

	req = addToken(req, authService.getAccessToken());

	return next(req).pipe(
		catchError((error) => {
			if (error.status !== 401 || isAuthRequest(req.url)) {
				return throwError(() => error);
			}

			if (!isRefreshing) {
				isRefreshing = true;
				return authService.refreshTokens().pipe(
					switchMap(() => {
						isRefreshing = false;
						return next(addToken(req, authService.getAccessToken()));
					}),
					catchError((refreshError) => {
						isRefreshing = false;
						authService.logout();
						return throwError(() => refreshError);
					})
				);
			}

			// Якщо refresh вже виконується — чекаємо завершення
			return refreshDone$.pipe(
				filter(Boolean),
				take(1),
				switchMap(() => next(addToken(req, authService.getAccessToken())))
			);
		})
	);
};

Хранение токенов

Токени зберігаються через StorageService, який є обгорткою над localStorage з SSR-безпечними перевірками через UniversalService. Ключі визначені в STORAGE_KEYS:

typescript
// Ключі (shared/constants/storage-keys.constant.ts)
export const STORAGE_KEYS = {
	ACCESS_TOKEN: "access_token",
	REFRESH_TOKEN: "refresh_token"
	// ...
} as const;

// Збереження (через AuthService.setTokens)
this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, accessToken);
this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, refreshToken);

// Отримання
const token = this.storage.getString(STORAGE_KEYS.ACCESS_TOKEN);

// Видалення (через AuthService.clearTokens)
this.storage.remove(STORAGE_KEYS.ACCESS_TOKEN);
this.storage.remove(STORAGE_KEYS.REFRESH_TOKEN);

SSR

StorageService використовує UniversalService.isBrowser для перевірки оточення перед роботою з localStorage, що необхідно для коректної роботи SSR.

JWT Claims

Токен можно декодировать для получения claims:

typescript
import { jwtDecode } from "jwt-decode";

const claims = this.authService.getTokenClaims();
// claims содержит: userId, email, exp, iat и т.д.

Безопасность

Best Practices

Что делается:

  • Токены в localStorage (не в cookies)
  • HTTPS обязателен для production
  • Токены автоматически прикрепляются к запросам
  • Автоматический logout при 401

Что НЕ делается:

  • XSS защита — ответственность разработчика
  • CSRF — не применимо при использовании JWT без cookies

Рекомендации

  1. Всегда используйте HTTPS в production
  2. Устанавливайте короткое время жизни access токена
  3. Используйте refresh токен для обновления
  4. Валидируйте токены на backend'е

API Endpoints

МетодEndpointОписание
POST/auth/sign-inВход в систему
POST/auth/sign-upРегистрация
GET/auth/meПолучить текущего пользователя
POST/auth/refreshОбновить токен (если реализовано)

Следующие шаги

SaaS Admin Documentation