В Angular 17 Angular Universal було переміщено в репозиторій angular-cli як пакет @angular/ssr
. Я зацікавився, як реалізувати авторизацію в цій новій конфігурації. Однією з проблем було те, що об'єкти запиту та відповіді не були доступні або використовувались під час розробки з локальним сервером. Це було вирішено в Angular 19 з введенням гібридного рендерингу. Тепер можна імпортувати токени запиту та відповіді з пакету @angular/core
.
Для мене тема авторизації в Angular 19 з SSR була цікавою з двох ключових причин:
- До
@angular/ssr
додатки Angular зазвичай не використовували сесії для авторизації — облікові дані зберігалися на стороні клієнта. Однак з SSR в Angular 19 тепер потрібно перевіряти на сервері, чи авторизований користувач, щоб вирішити, чи слід рендерити сторінку на сервері. - З міркувань безпеки рекомендується не зберігати облікові дані на клієнті. Натомість облікові дані повинні зберігатися в безпечних cookies тільки для HTTP, які автоматично надсилаються браузером з кожним запитом до бекенду. Це запобігає доступу клієнта до чутливих облікових даних і зменшує ризик атак типу XSS.
Налаштування SSR-сервера з гібридним рендерингом
Щоб отримати доступ до запиту, спершу потрібно налаштувати ваш SSR-сервер для роботи з новим API гібридного рендерингу. Зверніть увагу, що ця функція знаходиться в розробці, тому вирішуйте, чи доцільно її використовувати для вашого проекту. Ось проста конфігурація:
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const app = express();
const angularApp = new AngularNodeAppEngine();
app.get(
'**',
express.static(browserDistFolder, {
maxAge: '1y',
index: 'index.html',
}),
);
app.get('**', (req, res, next) => {
angularApp
.handle(req)
.then((response) => (response ? writeResponseToNodeResponse(response, res) : next()))
.catch(next);
});
if (isMainModule(import.meta.url)) {
const port = process.env['PORT'] || 4000;
app.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
export const reqHandler = createNodeRequestHandler(app);
Додатково потрібно додати це налаштування у вашому файлі angular.json
під параметр будівельника:
"outputMode": "server"
Вказівка, які маршрути мають рендеритися на сервері
Тепер ви можете вказати, які маршрути будуть рендеритись на сервері (SSR), додавши provideServerRoutesConfig
у ваш файл app.config.server.ts
. Ось приклад того, як це налаштувати:
const serverConfig: ApplicationConfig = {
providers: [provideServerRendering(), provideServerRoutesConfig(serverRoutes)],
};
Для маршрутів, що вимагають авторизації (тобто тих, що мають охоронців), потрібно встановити режим рендерингу на server
. Це гарантує, що сторінка буде рендеритись на сервері, а не попередньо рендеритись (SSG). Ось приклад:
import { RenderMode, ServerRoute } from '@angular/ssr';
import { Route } from '@/core/enums/route';
export const serverRoutes: ServerRoute[] = [
{
path: Route.HOME,
renderMode: RenderMode.Server,
},
{
path: Route.SIGN_IN,
renderMode: RenderMode.Prerender,
},
{
path: Route.SIGN_UP,
renderMode: RenderMode.Prerender,
},
{
path: Route.NOT_FOUND,
renderMode: RenderMode.Prerender,
},
];
Управління cookies для авторизації
Для обробки авторизації я створив сервіс для керування cookies.
Хоча є бібліотеки, такі як ngx-cookie-service-ssr
для цього, я створив власне рішення.
@Injectable({
providedIn: 'root',
})
export class CookieService {
private platformId = inject(PLATFORM_ID);
private request = inject(REQUEST, { optional: true });
exist(key: StorageKey): boolean {
return this.get(key) !== null;
}
get(key: StorageKey): string | null {
const regExp = new RegExp(`(^| )${key}=([^;]+)`);
const cookie = this.platformCookie.match(regExp);
return cookie ? decodeURIComponent(cookie[1]) : null;
}
private get platformCookie(): string {
if (isPlatformBrowser(this.platformId)) {
return document.cookie;
}
return this.request?.headers?.get('cookie') ?? '';
}
}
Перевірка авторизації на сервері
Ключовим є перевірити, чи авторизований користувач на сервері, тому що якщо ми зробимо цю перевірку на клієнті, сторінка не буде гідратована, і ми втратимо всі переваги SSR. Пам'ятайте, що сторінки, що потребують авторизації, не можуть бути попередньо рендерені (SSG), оскільки вони не мають доступу до об'єкта REQUEST
на цих сторінках.
Тепер ось охоронець, щоб захистити ваші маршрути від несанкціонованого доступу:
const IS_AUTHORIZED_KEY = makeStateKey('isAuthorized');
export const authorizedRoutes: CanActivateFn = () => {
const cookieService = inject(CookieService);
const transferState = inject(TransferState);
const platformId = inject(PLATFORM_ID);
const router = inject(Router);
if (isPlatformServer(platformId)) {
const isAuthorized = cookieService.exist(StorageKey.TOKEN);
transferState.set(IS_AUTHORIZED_KEY, isAuthorized);
return isAuthorized;
}
const isAuthorized = transferState.get(IS_AUTHORIZED_KEY, false);
if (!isAuthorized) {
return router.parseUrl(Route.SIGN_IN);
}
return isAuthorized;
};
Як це працює
На сервері ми перевіряємо, чи авторизований користувач, а потім передаємо цю інформацію на клієнтську сторону. Цей процес передачі необхідний, оскільки охоронець працює двічі — спочатку на сервері, потім на клієнті. Передаючи інформацію на клієнт, ми забезпечуємо, щоб перевірка авторизації працювала стабільно з обох боків.
Перекладено з: Authorization in Angular 19 with SSR