Авторизация в TypeScript/Angular
Клиентские приложения ПОЛИНОМ:MDM авторизуются на web-сервере с помощью JWT-токенов. В SDK
авторизация реализована библиотекой @ascon/polynom-api (сервисы LoginService, PolynomApiConfig,
AuthStore и фабрика SignInRequestFactory), а готовое окно входа — библиотекой @ascon/polynom-login.
ВНИМАНИЕ: срок действия токена доступа истекает через 10 минут после его создания. Для продолжения
работы используется токен обновления, который применяется автоматически методом LoginService.updateToken().
Подключение библиотек
Рекомендуемые зависимости:
{
"dependencies": {
"@ascon/core": "24.1.126",
"@ascon/polynom-api": "~24.1.14404",
"@ascon/polynom-api-client": "~24.1.14404",
"@ascon/polynom-login": "~24.1.14404",
"@ascon/primeng": "24.0.2",
"@ascon/qwebchannel-bridge": "^6.8.3",
"@ascon/ui": "24.1.126"
}
}
Библиотеки @ascon/* можно получить из каталога поставки SDK/Web/Samples/Client/libs/@ascon
или из npm-регистра.
Настройка адреса web-сервера и типа клиента
Адрес web-сервера и тип клиента задаются через сервис PolynomApiConfig:
import { inject } from "@angular/core";
import { ClientTypes } from "@ascon/polynom-api-client";
import { PolynomApiConfig } from "@ascon/polynom-api";
const polynomApiConfig: PolynomApiConfig = inject(PolynomApiConfig);
// Адрес web-сервера ПОЛИНОМ:MDM
polynomApiConfig.setHost("http://localhost:5000");
// Тип клиента влияет на получение лицензии
polynomApiConfig.setClientType(ClientTypes.Client);
Инициализация приложения
При старте приложения токен доступа обновляется до первого обращения к API. Для этого используется
APP_INITIALIZER:
// app.config.ts
import { APP_INITIALIZER, ApplicationConfig } from "@angular/core";
import { AuthStore, LoginService, PolynomApiConfig } from "@ascon/polynom-api";
import { appInitializer } from "./helpers/app.initializer";
export const appConfig: ApplicationConfig = {
providers: [
// ... прочие провайдеры
{
provide: APP_INITIALIZER,
useFactory: appInitializer,
deps: [LoginService, PolynomApiConfig, AuthStore],
multi: true
}
]
};
// helpers/app.initializer.ts
import { LoginService, PolynomApiConfig } from "@ascon/polynom-api";
import { ClientTypes } from "@ascon/polynom-api-client";
import { delay, lastValueFrom, take, TeardownLogic } from "rxjs";
import { environment } from "../environments/environment";
export async function initializeClient(loginService: LoginService): Promise<TeardownLogic> {
// Преобразуем Observable в Promise, чтобы ошибка автоматически превратилась в rejected promise
await lastValueFrom(loginService.updateToken().pipe(take(1), delay(500)));
}
export function appInitializer(loginService: LoginService, config: PolynomApiConfig): () => Promise<TeardownLogic> {
config.setClientType(ClientTypes.Client);
config.setHost(environment.apiRoute);
return async () => {
try {
return await initializeClient(loginService);
} catch (error) {
console.error("App initialization failed:", error);
}
};
}
Использование токена в запросах к API
Токен доступа добавляется в заголовок Authorization HTTP-интерцептором. Отдельный заголовок
с идентификатором хранилища не требуется: сессия однозначно определяется токеном доступа.
import { HttpHeaders, HttpInterceptorFn } from "@angular/common/http";
import { inject } from "@angular/core";
import { LoginService } from "@ascon/polynom-api";
export const jwtInterceptor: HttpInterceptorFn = (request, next) => {
const loginService = inject(LoginService);
// Получаем токен авторизации из локального хранилища
const polynomAccessToken = loginService.accessToken;
if (polynomAccessToken) {
const clonedRequest = request.clone({
headers: new HttpHeaders({
Authorization: `Bearer ${polynomAccessToken}`
})
});
return next(clonedRequest);
}
return next(request);
};
Регистрация интерцептора:
import { provideHttpClient, withInterceptors } from "@angular/common/http";
// в providers
provideHttpClient(withInterceptors([jwtInterceptor]));
Готовое окно авторизации
Библиотека @ascon/polynom-login предоставляет готовое окно входа AuthenticationPageComponent.
Оно выполняет выбор хранилища, вход по логину и паролю, вход от системной учётной записи, вход
по внешнему токену доступа (в том числе через общий сервис авторизации) и сохраняет последние
использованные данные.
Размещение окна на отдельном маршруте:
import { Route } from "@angular/router";
import { AuthenticationPageComponent } from "@ascon/polynom-login";
export const appRoutes: Route[] = [
{ path: "sign-in", component: AuthenticationPageComponent }
];
При интеграции в веб-клиент ПОЛИНОМ:MDM окно автоматически получает переданные идентификатор
хранилища и токен доступа через IntegrationHostService из @ascon/polynom-integration.
Программная авторизация
Если готовое окно не используется, авторизацию можно выполнить сервисом LoginService.
Получение списка зарегистрированных хранилищ
import { inject } from "@angular/core";
import { take } from "rxjs";
import { IStorageDefinition, LoginApiClient } from "@ascon/polynom-api-client";
private readonly _loginApiClient: LoginApiClient = inject(LoginApiClient);
requestStorageDefinition(): void {
this._loginApiClient
.storageDefinitions()
.pipe(take(1))
.subscribe({
next: (storageDefinitions: IStorageDefinition[]) => {
// Выбрать нужное хранилище из списка
},
error: (err) => {
console.error(err);
}
});
}
Определение IStorageDefinition
export type IStorageDefinition = {
// Отображаемое имя хранилища.
displayName: string;
// Идентификатор хранилища.
storageId: string;
};
Вход по имени входа и паролю
Тело запроса формируется фабрикой SignInRequestFactory, которая проставляет тип клиента и имя модуля:
import { inject } from "@angular/core";
import { take } from "rxjs";
import { ClientTypes } from "@ascon/polynom-api-client";
import { LoginService, SignInRequestFactory } from "@ascon/polynom-api";
private readonly _loginService: LoginService = inject(LoginService);
signInByPassword(storageId: string, login: string, password: string): void {
const request = SignInRequestFactory.createByPassword(
storageId,
login,
password,
ClientTypes.Client
);
this._loginService
.signIn(request)
.pipe(take(1))
.subscribe({
next: () => {
// Сохранять токены отдельно не нужно: LoginService сделает это автоматически
},
error: (err) => {
console.error(err);
}
});
}
Вход по имеющемуся токену доступа
Метод применяется при интеграции, когда токен доступа получен извне (например, от общего сервиса авторизации):
import { ClientTypes } from "@ascon/polynom-api-client";
import { SignInRequestFactory } from "@ascon/polynom-api";
signInByAccessToken(accessToken: string): void {
const request = SignInRequestFactory.createByAccessToken(ClientTypes.Client);
this._loginService
.signInByAccessToken(accessToken, request)
.pipe(take(1))
.subscribe({
next: () => {
// Действия после успешной авторизации
},
error: (err) => console.error(err)
});
}
Проверка состояния авторизации
import { Observable } from "rxjs";
// Поток состояния авторизации
get authorized$(): Observable<boolean> {
return this._loginService.authenticated$;
}
// Текущее значение
const authorized: boolean = this._loginService.authenticated;
Обновление токена доступа
this._loginService
.updateToken()
.pipe(take(1))
.subscribe({
next: () => {
// Токены доступа и обновления обновлены
},
error: (err) => console.error(err)
});
Выход
this._loginService
.signOut()
.pipe(take(1))
.subscribe({
next: () => {
// Сессия закрыта, сохранённые токены удалены
},
error: (err) => console.error(err)
});
См. пример SDK/Web/Samples/Client/polynom-select-demo.
Прямые запросы к REST API
Если библиотека не используется, авторизацию можно выполнить прямыми запросами к REST API
web-сервера. Описание методов /login/storage-definitions, /login/sign-in, /login/update-token
и /login/sign-out приведено в статье Авторизация в WEB API ПОЛИНОМ:MDM.
В заголовках последующих запросов нужно передавать Authorization: Bearer <access_token>.