Einführung: Sicherheit ist eine Anforderung, keine Option. Moderne Angular-Anwendungen verarbeiten vertrauliche Daten: Benutzerprofile, Unternehmens-Dashboards, Admin-Panels. Wenn Sie diese Abschnitte ungeschützt lassen, ist Ihre Anwendung unbefugtem Zugriff, Token-Diebstahl und Datenschutzverletzungen ausgesetzt. Angular bietet ein integriertes und zusammensetzbares Routing-Sicherheitssystem: Route Guards und HTTP Interceptors. Dieser Leitfaden zeigt Ihnen, wie Sie mit modernen Angular 17+-Tools ein mehrschichtiges Sicherheitssystem aufbauen. Wir werden uns jeden Mechanismus im Detail ansehen, mit echten Codebeispielen, die für den Produktionseinsatz bereit sind. Themen: #Angular #Guards #Interceptor #WebDevelopment #FrontendDevelopment Frontend-Side-Sicherheitsarchitektur Bevor wir uns mit dem Code befassen, ist es wichtig zu verstehen, wie die Sicherheit in einer Angular-Anwendung geschichtet ist: Ebene 1 – Route Guards: Sie steuern, ob der Benutzer zu einer bestimmten Route navigieren kann, bevor die Komponente überhaupt erstellt wird. Ebene 2 – HTTP-Interceptoren: Fügen Sie jeder ausgehenden Anfrage Authentifizierungstoken hinzu und verarbeiten Sie Fehlerantworten (401, 403) global. Ebene 3 – Backend-Validierung: Das Backend muss immer Authentifizierung und Autorisierung validieren. Frontend-Sicherheit ist eine zusätzliche Verteidigung, nicht die einzige. Abschnitt 1: CanActivate – Routen schützen CanActivate ist der am häufigsten verwendete Schutz. Es wird ausgeführt, bevor Angular eine Route und die zugehörige Komponente aktiviert. Wenn false oder ein UrlTree zurückgegeben wird, wird die Navigation blockiert und der Benutzer wird umgeleitet. Ab Angular 15 ist der empfohlene Ansatz die Schutzfunktion ( CanActivateFn ) mit inject() anstelle von Klassen mit veralteten Schnittstellen. Dadurch entfällt der Boilerplate-Vorgang und die funktionale Abhängigkeitsinjektion wird voll ausgenutzt. Basic Authentication Guard // core/guards/auth.guard.
ts import { inject } from '@angular/core'; import { CanActivateFn, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthService } aus '../services/auth.service'; export const authGuard: CanActivateFn = ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot, ) => { const auth = inject(AuthService); const router = inject(Router); if (auth.isAuthenticated()) { return true; } // Die für die Umleitung nach der Anmeldung angeforderte URL beibehalten return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url }, }); }; Guard für die Admin-Rolle // core/guards/admin.guard.ts import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; import { AuthService } aus '../services/auth.service'; export const adminGuard: CanActivateFn = (route, state) => { const auth = inject(AuthService); const router = inject(Router); if (!auth.isAuthenticated()) { return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }); } if (!auth.hasRole('admin')) { return router.createUrlTree(['/403']); } return true; }; AuthService mit JWT und Signalen // core/services/auth.service.ts import { Injectable, signal, berechnet } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { tap } from 'rxjs/operators'; Schnittstelle TokenPayload { sub: string; E-Mail: Zeichenfolge; Rolle: 'admin' | 'Benutzer'; iat: Zahl; exp: Zahl; } Funktion decodeJwt(token: string): TokenPayload | null { try { const base64Payload = token.split('.')[1]; const decoded = atob(base64Payload.replace(/-/g, '+').replace(/_/g, '/')); JSON.parse(dekodiert) als TokenPayload zurückgeben; } Catch { return null; } } @Injectable({ bereitgestelltIn: 'root' }) export class AuthService { private static readonly TOKEN_KEY = 'portfolio_token'; private readonly _payload = signal(this.readStoredPayload()); readonly currentUser = this._payload.
asReadonly(); readonly isAdmin = berechnet(() => this._payload()?.role === 'admin'); Konstruktor(privater http: HttpClient, privater Router: Router) {} login(email: string, password: string) { return this.http .post<{ access_token: string }>('/api/auth/login', { email, password }) .pipe(tap(({ access_token }) => { localStorage.setItem(AuthService.TOKEN_KEY, access_token); this._payload.set(decodeJwt(access_token)); } logout(): void { localStorage.removeItem(AuthService.TOKEN_KEY); this._payload.set(null); this.router.navigate(['/login']); } getToken(): string | } isAuthenticated(): boolean token = this.getToken(); if (!token) return payload ? date.now() / 1000 + 30 : false; } hasRole(role: string): boolean { return this._payload()? } private readStoredPayload(): localStorage.getItem(AuthService.TOKEN_KEY); return token ? decodeJwt(token) : null; } } Abschnitt 2: CanMatch – Sicherheit für Lazy-Loaded-Formulare lädt Angular das JavaScript-Bundle nur dann herunter, wenn der Benutzer den Upload nicht sichert, indem er einfach die URL ändert. CanMatch löst dieses Problem: gibt false zurück, Angular wertet die Route nicht aus und lädt das zugehörige Bundle nie herunter inject(AuthService); const router = inject(Router); if (auth.isAuthenticated() && auth.hasRole('admin')) return true;
ts import { Routes } from '@angular/router'; import { authGuard } from './core/guards/auth.guard'; import { adminGuard } aus './core/guards/admin.guard'; import { canMatchAdmin } from './core/guards/can-match-admin.guard'; export const APP_ROUTES: Routes = [ { path: 'admin', loadChildren: () => import('./features/admin/admin.routes').then(m => m.ADMIN_ROUTES), canActivate: [adminGuard], canMatch: [canMatchAdmin], }, { path: 'dashboard', loadComponent: () => import('./features/dashboard/dashboard.component').then(c => c.DashboardComponent), canActivate: [authGuard], }, { path: 'login', loadComponent: () => import('./features/auth/login.component').then(c => c.LoginComponent) }, { path: '', restartTo: 'dashboard', pathMatch: 'full' }, { path: '**', restartTo: 'login' }, ]; Abschnitt 3: CanDeactivate – Schutz nicht gespeicherter Formulare Ein häufiges Szenario: Der Benutzer füllt ein langes Formular aus und klickt versehentlich auf einen Navigationslink. Ohne CanDeactivate gehen alle Änderungen stillschweigend verloren. Bei diesem Blick erscheint eine Bestätigungsaufforderung. Guard CanDeactivate Generisch und wiederverwendbar // core/guards/dirty-form.guard.ts import { CanDeactivateFn } from '@angular/router'; Exportschnittstelle DirtyFormComponent { isDirty(): boolean; } export const dirtyFormGuard: CanDeactivateFn = Component => { if (!component.isDirty()) return true; return window.confirm('Sie haben nicht gespeicherte Änderungen, die verloren gehen. Fortfahren?'); }; Komponente mit Responsive Form // Features/profile/profile-edit.component.ts import { Component, OnInit } from '@angular/core'; import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { DirtyFormComponent } aus '../../core/guards/dirty-form.guard'; @Component({ selector: 'app-profile-edit', standalone: true, imports: [ReactiveFormsModule], templateUrl: './profile-edit.component.html' }) export class ProfileEditComponent implementiert OnInit, DirtyFormComponent { readonly form = this.
fb.group({ displayName: ['', [Validators.required, Validators.minLength(2)]], bio: ['', Validators.maxLength(500)], }); private saveSnapshot = ''; Konstruktor(private fb: NonNullableFormBuilder) {} ngOnInit(): void { this.savedSnapshot = JSON.stringify(this.form.getRawValue()); } save(): void { this.savedSnapshot = JSON.stringify(this.form.getRawValue()); } isDirty(): boolean { return JSON.stringify(this.form.getRawValue()) !== this.savedSnapshot; } } Abschnitt 4: HTTP-Interceptoren – kanalübergreifende Sicherheit HTTP-Interceptoren fangen jede ausgehende HTTP-Anfrage und jede eingehende Antwort ab. Sie sind der ideale Ort, um das JWT-Token hinzuzufügen, automatische Aktualisierungen zu verwalten und die Fehlerbehandlungslogik zu zentralisieren. JWT Authentication Interceptor // core/interceptors/auth.interceptor.ts import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http'; import { inject } from '@angular/core'; import { CatchError, ThrowError } from 'rxjs'; import { AuthService } aus '../services/auth.service'; import { Router } from '@angular/router'; export const authInterceptor: HttpInterceptorFn = (req, next) => { const auth = inject(AuthService); const router = inject(Router); const token = auth.getToken(); constauthorizedReq = token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }): req; return next(authorizedReq).pipe( CatchError((err: HttpErrorResponse) => { if (err.status === 401) { auth.logout(); router.navigate(['/login'], { queryParams: { reason: 'session-expired' } }); } if (err.status === 403) { router.navigate(['/403']); } return throwError(() => err }); import { HttpInterceptorFn } from '@angular/core'; import { LoadingService } from '../services/loading.service'; export const loadingInterceptor: HttpInterceptorFn = (req, next) => { if (req.
headers.has('X-Background-Request')) return next(req); const Loading = inject(LoadingService); Laden.Inkrement(); return next(req).pipe(finalize(() => Loading.decrement())); }; Anmelden app.config.ts // app.config.ts import { ApplicationConfig } from '@angular/core'; import { ProvideRouter, withComponentInputBinding } from '@angular/router'; import { ProvideHttpClient, withInterceptors, withFetch } from '@angular/common/http'; importiere { APP_ROUTES } aus './app.routes'; import { authInterceptor } from './core/interceptors/auth.interceptor'; import {loadingInterceptor} aus './core/interceptors/loading.interceptor'; export const appConfig: ApplicationConfig = { Provider: [ ProvideRouter(APP_ROUTES, withComponentInputBinding()), ProvideHttpClient(withFetch(), WithInterceptors([LoadingInterceptor, authInterceptor])), ], }; Best Practices für Sicherheit und Leistung Doppelte Backend-Verifizierung: Wächter schützen die UX, aber jeder API-Endpunkt muss das JWT unabhängig validieren. Verlassen Sie sich niemals nur auf das Frontend. Token mit kurzem Ablauf: Verwenden Sie JWTs mit einem Ablauf von 15–60 Minuten in Kombination mit Aktualisierungstoken mit langem Ablauf. Geben Sie keine sensiblen Daten in das JWT ein: Die Nutzlast ist Base64-codiert und nicht verschlüsselt. Geben Sie nur ID, Rolle und Frist an. Konfigurieren Sie CORS richtig: Verwenden Sie origin: '*' nicht in der Produktion. Verwenden Sie CanMatch für jedes sensible Lazy-Loaded-Modul – verhindert, dass das Bundle heruntergeladen und nicht nur angezeigt wird. Zentralisieren Sie die HTTP-Fehlerbehandlung im Interceptor: Vermeiden Sie die Duplizierung der Anmeldeumleitungslogik in jedem Dienst oder jeder Komponente. Fazit: Die Sicherheit einer Angular-Anwendung ist in Schichten aufgebaut. Route Guards (CanActivate, CanMatch, CanDeactivate) schützen die Navigation und verhindern das Laden vertraulicher Codes. HTTP-Interceptoren stellen sicher, dass jede Kommunikation mit dem Backend auf authentifizierte Weise erfolgt.
Denken Sie daran: Frontend-Sicherheit ist die erste Verteidigungslinie für den Benutzer, aber die wahre Sicherheit liegt im Backend. Wenn Sie beide Ebenen verwenden, wird Ihr System robust und professionell sein.