Kategorien
Angular PHP Security

XSRF Schutz in Angular

Cross-Site Request Forgery (XSRF), auch als Session Riding bekannt, ist ein weit verbreitetes Sicherheitsproblem, das Webanwendungen betrifft, einschließlich Angular-Anwendungen. XSRF-Attacken zielen darauf ab, die Identität und Berechtigungen eines unwissenden Nutzers zu missbrauchen, um bösartige Aktivitäten durchzuführen. Angular bietet jedoch eingebaute Mechanismen zur Bekämpfung solcher Angriffe. In diesem Artikel werden wir XSRF in Angular eingehender betrachten und darüber diskutieren, wie wir uns davor schützen können.

Ablauf einer XSRF Attacke

Bei einer XSRF-Attacke sendet der Angreifer eine betrügerische Anfrage von einer Seite, der das Opfer vertraut. Der Angreifer nutzt die Tatsache aus, dass Cookies beim Senden von Anfragen an eine Site automatisch inkludiert werden, auch wenn die Anfrage von einer dritten Site ausgelöst wird. Wenn das Opfer zu diesem Zeitpunkt bei der betroffenen Anwendung angemeldet ist, wird die Anfrage als legitime Anfrage des Opfers angesehen und ausgeführt. Dies könnten zum Beispiel ein Request an einen Bank Server sein, der eine Überweisung auslöst.

Implementierung des XSRF-Schutzes in Angular

Hier sind die Schritte, die Sie befolgen sollten, um XSRF-Schutz in Ihrer Angular-Anwendung zu implementieren:

  1. HttpClientModule importieren: Sie müssen das HttpClientModule importieren in der src/app/app.module.ts und für die Requests verwenden.
import {HttpClientModule} from "@angular/common/http";
...
imports: [
  HttpClientModule,
]
  1. XSRF-Token setzen: Der Server sollte das XSRF-Token in einem Cookie setzen. Das Standardcookie, das Angular sucht, heißt XSRF-TOKEN.
  2. XSRF-Tokens in HTTP-Anfragen verwenden: Der HttpXsrfInterceptor fügt automatisch das X-XSRF-TOKEN-Header mit dem XSRF-Token aus dem Cookie hinzu. Wenn der Server das Header erhält, vergleicht er das Token mit dem, was er hat. Wenn es nicht übereinstimmt, wird die Anfrage abgelehnt.

Es ist wichtig zu beachten, dass XSRF-Schutz in Angular darauf beruht, dass der Server korrekt konfiguriert ist, um das XSRF-Token in einem Cookie zu setzen und dieses bei eingehenden Anfragen zu überprüfen.

Wenn der Backend Server einen anderen Cookie oder Header für den XRSF Token verwendet, kann dies in Angular folgender Maßen konfiguriert werden:

imports: [
  HttpClientModule,
  HttpClientXsrfModule.withOptions({
    cookieName: 'My-Xsrf-Cookie',
    headerName: 'My-Xsrf-Header',
  }),
],

Angular fügt das XSRF-Token standardmäßig nur zu den PUT, POST, DELETE und PATCH HTTP-Anfragen hinzu, da diese Operationen im Allgemeinen den Zustand des Servers ändern können und daher als potenziell unsicher angesehen werden.

GET– und HEAD-Anfragen dagegen sind idempotent und lesen nur Daten, sie führen keine Aktionen aus, die den Zustand des Servers verändern, daher fügt Angular diesen Anfragen standardmäßig kein XSRF-Token hinzu. Das ist eine gängige Praxis, um die Menge der übertragenen Daten zu reduzieren und da XSRF-Angriffe typischerweise auf Zustandsänderungen abzielen.

Beispiel Implementierung im Backend mit PHP

Hier ist ein einfaches Beispiel in PHP für das Setzen und Überprüfen eines XSRF-Tokens.

Token setzen

Wenn ein Benutzer eine Sitzung startet, erstellen und speichert man ein XSRF-Token in einem Cookie und überträgt ihn damit an das Frontend.

<?php
session_start();

if (empty($_SESSION['token'])) {
    $token = bin2hex(random_bytes(32));
    $_SESSION['token'] = $token;
    setcookie("XSRF-TOKEN", $token, time() + 3600, "/");
}

Token überprüfen

Bei eingehenden POST, PUT, DELETE oder PATCH Anfragen vom Frontend, überprüft man das XSRF-Token im Backend.

<?php
session_start();

$method = $_SERVER['REQUEST_METHOD'];
$serverToken = $_SESSION['token'];
$clientToken = $_SERVER['HTTP_X_XSRF_TOKEN'];

if (in_array($method, array('POST', 'PUT', 'DELETE', 'PATCH'))) {
    if (!isset($clientToken)) {
        die("Unauthorized request (missing X-XSRF-TOKEN header)");
    } elseif (!hash_equals($serverToken, $clientToken)) {
        die("Unauthorized request (X-XSRF-TOKEN does not match)");
    }
}
Kategorien
Angular Security

HTTPOnly und Secure Cookies in Angular

HTTPOnly und Secure Flags sind Optionen, die vom Server verwendet werden, um die Sicherheit von Cookies zu erhöhen.

Ein HTTPOnly Cookie kann nicht von JavaScript-Code gelesen werden; es wird nur an den Server geschickt. Das hilft, sich gegen Cross-Site Scripting (XSS) Angriffe zu schützen.

Ein Secure Cookie wird nur über eine sichere HTTPS-Verbindung gesendet. Das hilft, sich gegen Man-in-the-middle-Angriffe zu schützen.

Die Cookies können mit bspw. PHP so gesetzt werden:

setcookie('foo', 'bar', [
    'expires' => time() + 3600, // 1h gültig
    'path' => "/", // Verfügbar für die gesamte Website
    'secure' => true, // Nur über HTTPS übertragen
    'httponly' => true, // Nur über HTTP/HTTPS, kein Zugriff für JavaScript
]);

Cookies mit Angular

Sie können zwar in Angular Cookies setzen können, aber die Flags Secure und HttpOnly können nur serverseitig gesetzt werden. Deshalb sollten Sie immer sorgfältig abwägen, welche Informationen Sie in clientseitigen Cookies speichern. Allgemein sollten keine sensiblen Informationen in solchen unsicheren Cookies gespeichert werden. Sie sollten niemals direkt sensible Daten wie Passwörter oder personenbezogene Daten in Cookies speichern. Stattdessen sollten Sie eine Session-ID speichern, die auf dem Server verwendet wird, um den entsprechenden Benutzer oder die entsprechenden Daten zu identifizieren.

Wie sollte der Session Cookie gespeichert werden?

Session-Cookies enthalten normalerweise sensitive Informationen und sollten daher sicher gespeichert werden. Hier sind einige allgemeine Richtlinien zum Speichern von Session-Cookies:

  1. HttpOnly: Das HttpOnly-Flag sollte immer gesetzt werden. Dies bedeutet, dass das Cookie nicht von clientseitigem JavaScript abgerufen und geändert werden kann, was hilft, Cross-Site-Scripting-(XSS)-Angriffe zu verhindern.
  2. Secure: Das Secure-Flag sollte immer gesetzt werden. Dies bedeutet, dass das Cookie nur über eine HTTPS-Verbindung gesendet wird, was hilft, Man-in-the-middle-Angriffe zu verhindern.
  3. SameSite: Das SameSite-Attribut sollte, wenn möglich, auf „Strict“ oder zumindest auf „Lax“ gesetzt werden. Dies verhindert, dass das Cookie bei Cross-Site-Requests gesendet wird, was hilft, Cross-Site-Request-Forgery-(CSRF)-Angriffe zu verhindern.

Die Cookie SameSite-Eigenschaften im Allgemeinem

SameSite ist ein Attribut von Cookies, das angibt, wie sie in Cross-Site-Anfragen behandelt werden sollen. Es gibt drei mögliche Werte für die SameSite-Eigenschaft: „Strict“, „Lax“ und „None“. Jeder Wert hat unterschiedliche Auswirkungen auf das Verhalten von Cookies. Leider kann man nicht generell einen Default Wert verwenden, sondern sollte abhängig vom Use Case des einzelnen Cookie entscheiden, welcher am besten geeignet ist.

  1. SameSite=Strict:
    Wenn die SameSite-Eigenschaft auf „Strict“ gesetzt ist, wird das Cookie nur an dieselbe Ursprungsseite gesendet, von der es stammt. Das bedeutet, dass das Cookie nicht in Cross-Site-Anfragen, wie zum Beispiel Klicks auf Links von einer anderen Website, gesendet wird. Es bietet eine hohe Sicherheit, da es das Risiko von Cross-Site Request Forgery (CSRF) reduziert. Allerdings kann es dazu führen, dass einige Funktionen, die auf Cookies von Drittanbietern angewiesen sind, möglicherweise nicht mehr wie erwartet funktionieren.
  2. SameSite=Lax:
    Bei der SameSite-Eigenschaft „Lax“ werden Cookies in Cross-Site-Anfragen nur eingeschränkt gesendet. Das Cookie wird jedoch an die Zielseite gesendet, wenn es sich um einen „Top-Level-Navigation“ handelt, z.B. wenn der Benutzer auf einen Link klickt, der zu einer anderen Website führt. Das Cookie wird jedoch nicht gesendet, wenn die Anfrage durch eine „eingebettete“ Ressource ausgelöst wird, wie z.B. ein Bild oder ein Skript auf einer anderen Website. SameSite=Lax ist die empfohlene Einstellung für die meisten Anwendungsfälle, da sie ein gutes Gleichgewicht zwischen Sicherheit und Funktionalität bietet.
  3. SameSite=None; Secure:
    Die SameSite-Eigenschaft „None“ erlaubt es dem Cookie, in Cross-Site-Anfragen gesendet zu werden, unabhängig davon, welche Seite die Anfrage auslöst. Dies ist nützlich, um bestimmte Funktionen zu ermöglichen, die Cookies von Drittanbietern erfordern, z.B. Single Sign-On über verschiedene Domänen hinweg.
  1. Anwendungsfall 1: Formularübermittlung / Session Cookie: In diesem Anwendungsfall wird ein Formular auf einer Website ausgefüllt und an denselben Ursprung gesendet. Das Cookie wird benötigt, um den Sitzungszustand aufrechtzuerhalten.
  2. Anwendungsfall 2: Cross-Site-Tracking: In diesem Anwendungsfall wird das Cookie verwendet, um das Verhalten eines Benutzers über verschiedene Websites hinweg zu verfolgen. Dies wird oft von Werbetreibenden und Analysetools verwendet, um personalisierte Werbung oder Analysen bereitzustellen.
  3. Anwendungsfall 3: Einbettung von Inhalten von Drittanbieter-Websites: In einigen Fällen kann es erforderlich sein, Inhalte von Drittanbieter-Websites, wie eingebettete Videos oder Social-Media-Widgets, in die eigene Website einzubetten. Um auf das Cookie der Drittanbieter-Website zuzugreifen und die gewünschten Funktionen bereitzustellen, kann die SameSite-Einstellung „None“ erforderlich sein.
Kategorien
Angular Security

Anwenden von HTTP Strict Transport Security (HSTS) in Angular

HTTP Strict Transport Security (HSTS) ist ein Sicherheitsmechanismus für HTTPS-Verbindungen und es schützt vor Aushebelung der Verbindungsverschlüsselung und Session Hijacking.

Der Server kann über den HTTP Response Header Strict-Transport-Security dem Browser mitteilen, verschlüsselte Verbindungen für eine definierte Zeit zu nutzen.

Der Browser wird jetzt alle Anfrage an den Server (Bilder, CSS, JS) automatisch mit HTTPS tätigen, auch wenn man im Code an einer Stelle einen Schreibfehler eingebaut hat wie hier:

<img src='http://foo.org/image.jpg'>  // mit HSTS geht der Request automatisch nach https://foo.org/image.jpg

Ein solcher Header sieht so aus:

Strict-Transport-Security: max-age=31536000

Damit wird dem Browser erklärt, nur verschlüsselte Verbindungen zu dieser Seite aufbauen soll innerhlab der nächsten 31536000 Sekunden (1 Jahr).

HSTS preload list

Ein Problem besteht darin, dass beim Erstaufruf einer Domain ein unverschlüsselter Request gesendet werden muss, um die Verschlüsselungsforderung des Servers zu prüfen. Um dieses Problem zu umgehen, gibt es die HSTS preload list, die von Google Chrome betreut und von anderen großen Webbrowsern genutzt wird. Wenn eine Domain in dieser Liste steht, wird die Erstanfrage übersprungen und die Kommunikation sofort verschlüsselt.

Die Eintragung zusätzlicher Domains in die HSTS preload list ist kostenlos möglich hier.

HSTS in Angular

HSTS muss im Webserver aktiviert werden und Angular braucht dafür keine Anpassungen.

Beispiel Implementation für nginx

Kategorien
Angular Security

Verwenden von Content Security Policy (CSP) in Angular

Content Security Policy (CSP) ist eine Sicherheitsmaßnahme, die von Webbrowsern genutzt wird, um bestimmte Arten von Angriffen zu verhindern, einschließlich Cross-Site Scripting (XSS) und Dateninjektionsangriffe. Sie funktioniert, indem sie Richtlinien für die Arten von Inhalten festlegt, die auf einer Webseite geladen und ausgeführt werden dürfen.

Die CSP wird vom Server als HTTP-Header gesendet. Ein typischer CSP-Header könnte folgendermaßen aussehen:

Content-Security-Policy: default-src 'self'; img-src 'self' https://example.com; script-src 'self' https://example.com;

Dieser Header legt eine Reihe von Richtlinien fest:

  • default-src 'self': Dies bedeutet, dass Inhalte (wie Skripte, Styles, Medien und mehr) nur von der aktuellen Domain geladen werden dürfen.
  • img-src 'self' https://example.com: Dies bedeutet, dass Bilder nur von der aktuellen Domain oder https://example.com geladen werden dürfen.
  • script-src 'self' https://example.com: Dies bedeutet, dass Skripte nur von der aktuellen Domain oder https://example.com geladen werden dürfen.

Diese Richtlinien helfen, Angriffe zu verhindern, indem sie den Ursprung von Inhalten, die in einer Webseite ausgeführt werden dürfen, einschränken. Beispielsweise würde eine CSP, die das Laden von Skripten nur von der aktuellen Domain erlaubt, verhindern, dass ein Angreifer ein Skript von einer externen Quelle in die Webseite einfügt.

Der CSP Nonce

In der Content Security Policy (CSP) kann eine Nonce (“number only used once”, in diesem Fall kann es auch ein alphanumerischer Wert sein) dazu verwendet werden, bestimmten Inline-Skripten oder Inline-Styles die Ausführung zu erlauben.

Jedes Mal, wenn die Seite geladen wird, generiert der Server eine neue eindeutige Nonce-Wert, die dann dem CSP-Header und dem entsprechenden Inline-Script oder -Style hinzugefügt wird.

Angenommen, Sie haben eine Webserver-Konfiguration (z.B. in Node.js), die in der Lage ist, eine zufällige Nonce zu generieren und in Ihre Seite einzufügen. Ihre CSP-Richtlinie könnte dann folgendermaßen aussehen:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-2726c7f26c'

In diesem Beispiel ist 'nonce-2726c7f26c' der Nonce-Wert. Dieser Wert würde dem Inline-Script-Tag in Ihrem HTML-Dokument hinzugefügt, so dass es trotz der CSP ausgeführt werden kann:

<script nonce="2726c7f26c">
    // JS Code hier
</script>

Wenn die Seite neu geladen wird, würde der Server eine neue Nonce generieren und sowohl den CSP-Header als auch das nonce-Attribut des Script-Tags aktualisieren. Das bedeutet, dass, selbst wenn ein Angreifer die aktuelle Nonce kennen würde, sie ihm nicht helfen würde, schädliche Skripte auszuführen, da die Nonce bei jedem Seitenladen geändert wird.

Der folgende Code würde vom Browser nicht ausgeführt werden als Schutz vor Angriffen:

<script>
    // Code wird geblockt
</script>

Die Reporting Funktion

Die Berichtsfunktion in der Content Security Policy (CSP) ermöglicht es Ihnen, Benachrichtigungen zu erhalten, wenn bestimmte Vorfälle, wie beispielsweise Verstöße gegen die CSP, auftreten. Dies wird über die report-uri oder die report-to Direktive in der CSP-Header-Zeichenkette gesteuert.

Wenn ein CSP-Verstoß auftritt, sendet der Browser einen POST-Request mit einem JSON-Body an die URL, die in der report-uri oder report-to Direktive angegeben ist.

Das JSON-Objekt enthält Details zum Verstoß, wie den Dokument-URI, den Verstoßenden URI, die Zeilen- und Spaltennummer, die die Verletzung verursacht hat, und die genaue CSP-Richtlinie, die verletzt wurde.

Hier ist ein Beispiel für eine CSP mit Berichtsfunktion:

Content-Security-Policy: default-src 'self'; report-uri /csp-violation-report-endpoint/

In diesem Fall würde der Browser, wenn ein Verstoß auftritt, einen POST-Request an /csp-violation-report-endpoint/ senden, der die Details des Verstoßes enthält.

Die Verknüpfung von Angular und dem Nonce Wert

Der Nonce Token wird vom Backend generiert und kann der Angular Anwendung bereit gestellt werden über:

import {bootstrapApplication, CSP_NONCE} from '@angular/core';
import {AppComponent} from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [{
    provide: CSP_NONCE,
    useValue: myNonceService.getValue()
  }]
});

Der Nonce Wert wird vom Backend gesetzt und beim ausliefern des Frontend Angular Projektes gesetzt über die index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <base href="/" />
  </head>
  <body>
    <app-root
      data-nonce="MY_NONCE_PLACEHOLDER"></app-root>
  </body>
</html>

Der Service Zum Auslesen sieht so aus:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class MyNonceService {
  getValue(): string {
    return document.querySelector<HTMLElement>('app-root')?.dataset['nonce'];
  }
}

Jetzt muss das Backend so konfiguriert werden, dass es

  1. Bei jedem Request auf den Angular Code einen eindeutigen Nonce generiert und in den Header packt.
  2. Alle vorkommen von MY_NONCE_PLACEHOLDER ersetzt werden durch den Wert des Nonce.

Beispiel nginx Konfiguration:

add_header Content-Security-Policy "default-src 'self'; script-src 'nonce-${request_id}'; style-src 'nonce-${request_id}';";

Damit fügt der nginx dem Header den Nonce Wert hinzu.

Die request_id Variable wird vom nginx bei jedem Request automatisch berechnet und steht global zur Verfügung.

location / {
    sub_filter MY_NONCE_PLACEHOLDER $request_id;
    sub_filter_once off;         
    }

Diese Direktive sorgt dafür, dass jede Instanz von „MY_NONCE_PLACEHOLDER“ im HTML-Inhalt der Seite durch den Nonce Wert ersetzt wird.

Der CSP Header für Angular

Ab Angular Version 16:

default-src 'self'; 
style-src 'self' 'nonce-randomNonceGoesHere'; 
script-src 'self' 'nonce-randomNonceGoesHere';

Vor Version 16 war es notwendig ‚unsafe-inline‘ hinzuzufügen für die style-src:

default-src 'self'; 
style-src 'self' 'unsafe-inline' 'nonce-randomNonceGoesHere'; 
script-src 'self'  'nonce-randomNonceGoesHere';
Kategorien
Angular JavaScript

Identifizieren von ungenutzten NPM Packeten mit depcheck in Angular

Das npm Packet depcheck kann ungenutzten NPM Packete finden. Diese Information ist wichtig, weil fremde Packete ein Sicherheitsrisiko darstellen.

Das Risiko geht von fremden npm Packeten

Es gibt verschiedene potenzielle Angriffe oder Sicherheitsrisiken, die mit npm-Paketen verbunden sein können. Hier sind einige Beispiele:

  1. Maliziöse Pakete: Angreifer können böswillige oder schädliche Pakete veröffentlichen, die als nützliche oder beliebte Bibliotheken getarnt sind. Diese Pakete können schädlichen Code enthalten, der dein System oder deine Anwendung beeinträchtigen kann.
  2. Abhängigkeitskette: Wenn du npm-Pakete in deinem Projekt verwendest, können diese wiederum von anderen Paketen abhängen. Angreifer könnten eine Schwachstelle in einer Abhängigkeit ausnutzen, um Zugriff auf dein System zu erlangen oder deine Anwendung zu kompromittieren.
  3. Verwaiste Pakete: Verwaiste Pakete sind solche, deren ursprüngliche Entwickler die Wartung eingestellt haben. Diese Pakete erhalten möglicherweise keine Sicherheitsupdates mehr und könnten anfällig für Angriffe sein. Verwenden deiner Anwendung veraltete und unsichere Pakete erhöht das Risiko von Sicherheitslücken.
  4. Manipulation von Paketregistrierungen: Ein Angreifer könnte versuchen, die Infrastruktur der Paketregistrierung zu kompromittieren und schädliche Pakete in den offiziellen Paketkanälen bereitzustellen. Dies könnte dazu führen, dass du unwissentlich gefährliche Pakete installierst.
  5. Code-Injektion: Wenn du npm-Pakete verwendest, ist es wichtig, darauf zu achten, dass der Code sicher ist. Ein fehlerhaftes oder unsicher implementiertes Paket kann Angreifern die Möglichkeit bieten, schädlichen Code in deine Anwendung einzuschleusen.

Die Funktionsweise von Depcheck

Depcheck analysiert den Quellcode deines Projekts, um festzustellen, welche Pakete tatsächlich verwendet werden. Dabei untersucht es verschiedene Aspekte des Codes, um die Verwendung von Paketen zu ermitteln:

  1. Importanweisungen: Depcheck überprüft die Importanweisungen in den JavaScript-/TypeScript-Dateien. Es sucht nach importierten Modulen, Klassen, Funktionen oder Variablen und ermittelt so, welche Pakete im Code verwendet werden.
  2. Referenzen und Verwendungen: Depcheck analysiert den Code, um festzustellen, ob die importierten Module, Klassen, Funktionen oder Variablen tatsächlich verwendet werden. Es überprüft, ob Funktionen aufgerufen, Methoden aufgerufen, Eigenschaften gelesen oder Variablen referenziert werden. Wenn ein importiertes Element nicht verwendet wird, markiert depcheck das zugehörige Paket als potenziell unbenutzt.
  3. Berücksichtigung verschiedener Dateitypen: Depcheck kann auch andere relevante Dateitypen analysieren, wie z.B. HTML-Dateien oder Konfigurationsdateien, um die Verwendung von Paketen zu ermitteln. Beispielsweise können in HTML-Dateien verwendete Direktiven oder Komponenten auf Pakete hinweisen, die im Code selbst möglicherweise nicht explizit importiert werden.

Einbinden von depcheck in Angular

Die Installation erfolgt über

npm install -g depcheck typescript

Beispiel Ausgabe bei einem Angular Projekt:

Unused dependencies
* @types/quill
* @types/vis
* bootstrap
* ngx-quill
* tslib
Unused devDependencies
* @angular-devkit/build-angular
* @angular/cli
* @angular/compiler-cli
* @types/jasmine
* jasmine-core
* karma
* karma-chrome-launcher
* karma-coverage
* karma-jasmine
* karma-jasmine-html-reporter
* typescript

In der package.json befanden sich 11 dev Dependencies und 19 normale Dependencies. Es wurden alle dev Dependencies falsch positiv erkannt. Von den potenziel gefährlicheren normalen Packete, die nicht nur im dev Modus existieren, wurden korrekt 13 Packete als verwendet erkannt. Die 5 Packete kann man einfach per Namen oder Regex (@types/*) entfernen.

Es wird tatsächlich erkannt, das ngx-quill in dem Beispiel als einziges Packet nicht verwendet wird.

Es muss also eine Menge von Hand ausgechlossen werden, wofür ein spezielle depcheck Angular Erweiterung nützlich wäre, aber leider noch nicht existiert im Gegensatz zu React JSX und Vue.js.

Fazit

Depcheck ist nützlich um unbenutzte Packete zu identifizieren, um die Sicherheit von Angular Anwendungen zu gewährleisten.

Außerdem sollten alle Packete fortwährend auf Sicherheitslücken gescannt werden. Wenn ein Packet nicht mehr weiter entwickelt wird oder Sicherheitslücken nicht schließt, sollte es ausgetauscht werden gegen eine aktuelle Variante.

Kategorien
Angular

Angular Relogin User on Wake Up

In einer Angular-App gibt es eine Möglichkeit, ein Ereignis auszulösen, wenn ein Benutzer den Browser-Tab wieder öffnet. Dies kann mithilfe des Page Visibility API erreicht werden, das vom Browser bereitgestellt wird.

Um auf das Ereignis zu reagieren, wenn der Benutzer den Tab wieder öffnet, kannst du das visibilitychange-Ereignis an das document-Objekt binden. Hier ist ein Beispiel, wie du dies in Angular umsetzen kannst:

import {Component, HostListener} from '@angular/core';
import {AuthService} from './shared/auth/auth.service';
import {Router} from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(
    private authService: AuthService,
    private router: Router,
  ) {}

  // when user is reopening app, we check if the API session is expired and user nees to login again
  @HostListener('document:visibilitychange', ['$event'])
  visibilitychange() {
    console.log('visibilitychange event');
    if (!document.hidden) {
      console.log('check if user is logged in');
      if (!this.authService.checkTokenValidity()) {
        console.log('user token validity has expired. make logout');
        this.router.navigate(['/logout']);
      }
    }
  }
}

Zusammenfassend wird dieser Code verwendet, um zu überprüfen, ob die API-Sitzung des Benutzers abgelaufen ist, wenn der Benutzer die App erneut öffnet. Wenn das Benutzertoken abgelaufen ist, wird der Benutzer automatisch zur Logout-Seite weitergeleitet. Dadurch wird eine erneute Authentifizierung ermöglicht und der Benutzer aufgefordert, sich erneut anzumelden, um auf geschützte Funktionen der App zugreifen zu können.

Kategorien
Angular

Angular Test HTML Code der Komponente ausgeben

Der HTML Code kann ausgegeben werden mit:

console.log(fixture.debugElement.nativeElement.innerHTML);
Kategorien
Angular

Angular Change Detection für Array Input

Die Change Detection von Angular wird immer ausgeführt bei einem Array Input, wenn sich die Referenz des Arrays ändert, aber nicht der Inhalt:

console.log([1] === [1]); // ergibt false

In Angular kann man die Change Detection beeinflussen, indem man die Strategie der Change Detection für eine Komponente ändert. Eine der Strategien ist die „OnPush“-Strategie, bei der die Change Detection nur dann ausgelöst wird, wenn sich die Eingangsparameter einer Komponente ändern oder wenn ein Ereignis aufgerufen wird.

Um sicherzustellen, dass die Change Detection nur ausgeführt wird, wenn das Input-Array geändert wurde, können Sie den OnPush-Strategie für die Komponente verwenden und eine Überwachungsfunktion für das Input-Array hinzufügen, die nur dann ausgelöst wird, wenn sich die Werte des Arrays ändern.

Hier ist ein Beispiel einer solchen Komponente:

import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-example',
  template: '...',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleComponent {
  @Input() items: any[] = [];

  constructor(private cd: ChangeDetectorRef) { }

  ngOnChanges(changes: any) {
    if (changes.items &amp;&amp; !this.arrayEquals(changes.items.previousValue, changes.items.currentValue)) {
      this.cd.markForCheck();
    }
  }

  private arrayEquals(a: any[], b: any[]): boolean {
    return JSON.stringify(a) == JSON.stringify(a);
  }
}

Somit wird die Komponente nur neu gerendert, wenn sich an dem Items Array wirklich etwas ändert und nicht nur die Referenz.

Kategorien
Angular

End-To-End-Tests mit Angular und Cypress

Um End-To-End-Tests mit Cypress und Angular zu schreiben, können Sie folgende Schritte ausführen:

  1. Stellen Sie sicher, dass Sie die neueste Version von Cypress und Angular installiert haben. Sie können Cypress mit dem Befehl npm install -D cypress installieren.
  2. Öffnen Sie Cypress mit dem Befehl npx cypress open im Hauptverzeichnis Ihres Projekts. Cypress öffnet eine Benutzeroberfläche, in der Sie Ihre Tests aufzeichnen und ausführen können.
  3. Erstellen Sie eine neue Testdatei, indem Sie auf „Add test file“ in der Cypress-Benutzeroberfläche klicken. Sie können die Testdatei auch manuell im Verzeichnis „cypress/integration“ erstellen.
  4. Fügen Sie den folgenden Code in Ihre Testdatei ein, um sicherzustellen, dass Cypress Ihre Angular-Anwendung erfolgreich geladen hat:
describe('My First Test', () => {
  it('Does not do much!', () => {
    cy.visit('http://localhost:4200');
    cy.contains('app-root h1', 'Welcome to my-app!');
  });
});
  1. Führen Sie den Test aus, indem Sie auf die Testdatei in der Cypress-Benutzeroberfläche klicken. Wenn alles erfolgreich war, sollte der Test grün markiert werden.
  2. Erstellen Sie weitere Tests, indem Sie Schritte und Assertions hinzufügen. Sie können Elemente auf der Seite auswählen und auf sie interagieren, indem Sie die Methoden von Cypress verwenden, z.B. cy.get(), cy.click() und cy.type().

Um Cypress mit einem Docker-Container zu verwenden, können Sie folgende Schritte ausführen:

  1. Erstellen Sie eine Dockerfile in Ihrem Projektverzeichnis mit den folgenden Inhalten:
FROM cypress/browsers:node18.6.0-chrome105-ff104

# Install your application's dependencies
COPY package.json yarn.lock ./
RUN yarn

# Add your application code
COPY . ./

# Run the tests
CMD ["yarn", "cy:run"]
  1. Erstellen Sie ein Docker-Image mit dem Befehl docker build -t my-app .
  2. Führen Sie einen Container auf Basis des Images mit dem Befehl docker run -it --rm -v $(pwd):/app my-app aus. Dies führt die Cypress-Tests innerhalb des Containers aus.

Durch das Hinzufügen von -v $(pwd):/app werden Ihre Projektdateien in den Container gemountet und Cypress kann auf sie zugreifen.

Kategorien
Angular

Angular Unit Testing von Komponenten mit Abhängigkeiten

Um einen Unit Test von einer Angular Komponente mit Abhängigkeiten (Dependencies) schreiben zu können, muss man die Abhängigkeiten unter Kontrolle bringen.

Wenn man dies nicht tut, schreibt man einen Integration-Test. Integration-Tests haben eine höhere Komplexität hat und gehen schneller kaputt.

Beipsiel einer Komponente „MyComponent“ mit einer Abhängikeit „MyService“:

export class MyComponent implements OnInit {

  public serviceValue;

  constructor(
    private myService: MyService,
  ) {}

  ngOnInit() {
    this.serviceValue =  this.myService.getValue();
  }
}

Um in einem Jasmine Unit Test die Abhängigkeit von MyService zu kontrollieren, kann man einen Spy einsetzen, der an die die Stelle der Methode MyService::getValue gesetzt wird und die Sachen macht, die man für den Test gern hätte.

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
  let myService:MyService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [MyComponent,],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    myService = TestBed.inject(MyService);
  });

  it('spy should set value', () => {
     spyOn(myService,'getValue').and.returnValue('foo');
    fixture.detectChanges();
    expect(component.serviceValue).toBe('foo');
  });
});

In dem Beispiel wird die Methode getValue der Klasse MyService mit einem Spy überschrieben und dem Spy mitgeteilt, dass er den Wert ‚foo‘ zurückgeben soll.