CORS

Cross-Origin Resource Sharing

22.02.2024

securityde
Gregor Wedlich
Gregor Wedlich
Life, the Universe and Everything.
Donate with:
Lightning
Alby

Inhaltsverzeichnis

    Im Ökosystem der modernen Webentwicklung ist die nahtlose Integration von Frontend und Backend über verschiedene Ursprünge hinweg ein wesentliches Element. Doch diese Integration stellt Entwickler:innen oft vor eine Herausforderung: die 'Same-Origin Policy' (SOP), eine Sicherheitsmaßnahme, die verhindert, dass eine Webseite Skripte ausführt, die auf Daten von einem anderen Ursprung als dem eigenen zugreifen. Hier kommt CORS, oder Cross-Origin Resource Sharing, ins Spiel.

    CORS ist eine Technik, die es Webanwendungen ermöglicht, die Einschränkungen der SOP zu umgehen und sicher auf Ressourcen von anderen Ursprüngen zuzugreifen. Es ist ein Konzept, das in der heutigen vernetzten Welt, in der APIs und externe Dienste eine zentrale Rolle spielen, von großer Bedeutung ist. Bevor CORS weit verbreitet war, griffen Entwickler:innen oft auf Lösungen wie JSONP (JSON with Padding) zurück, die zwar funktional, aber nicht ohne Sicherheitsbedenken waren.

    In diesem Artikel werden wir uns anschauen, wie CORS funktioniert, warum es für die Entwicklung moderner Webanwendungen unerlässlich ist und wie es in Node.js mit dem CORS-Paket implementiert wird. Von den Grundlagen der 'Simple Requests' bis hin zu den komplexeren 'Preflight-Requests' decken wir alle wichtigen Aspekte ab, die wir als Entwickler:in wissen müssen, um die Kommunikation zwischen verschiedenen Ursprüngen sicher und effizient zu gestalten.

    Einführung in CORS (Cross-Origin Resource Sharing) und JSONP (JSON with Padding)

    Im Bereich der Webentwicklung ist das Konzept von Cross-Origin Resource Sharing (CORS) von zentraler Bedeutung. Es ermöglicht Webanwendungen, die Einschränkungen der 'Same-Origin Policy' (SOP) zu überwinden.

    Vor der Etablierung von CORS griffen Entwickler:innen oft auf JSONP (JSON with Padding) zurück, um Cross-Origin-Datenabrufe zu ermöglichen. JSONP nutzte Skript-Tags, um Daten von anderen Ursprüngen zu laden, indem es die Einschränkungen der SOP umging. Obwohl diese Methode funktionierte, war sie nicht ohne Mängel. Sicherheitsbedenken, wie die Anfälligkeit für Cross-Site Scripting (XSS) Angriffe, und Designeinschränkungen, wie die Begrenzung auf GET-Anfragen, machten JSONP zu einer suboptimalen Lösung.

    CORS hingegen bietet eine sicherere und flexiblere Alternative. Durch das Definieren spezifischer Regeln und Header ermöglicht CORS Webseiten den sicheren Zugriff auf Ressourcen über Ursprünge hinweg. Es erlaubt Entwickler:innen, moderne Webanwendungen zu erstellen, die nahtlos Dienste und APIs von verschiedenen Ursprüngen integrieren können. Von der einfachen Abfrage von Daten bis hin zu komplexen API-Interaktionen hat CORS die Art und Weise, wie Webanwendungen kommunizieren, revolutioniert.

    Grundlagen und Anwendungsfälle von CORS

    CORS (Cross-Origin Resource Sharing) ist eine Technik, die ein grundlegendes Problem in der Webentwicklung adressiert, welches durch die 'Same-Origin Policy' (SOP) entsteht. Die SOP dient als Sicherheitsmaßnahme, indem sie festlegt, dass Skripte auf einer Webseite nur auf Ressourcen desselben Ursprungs zugreifen dürfen. Obwohl dies eine wichtige Sicherheitsvorkehrung ist, kann es die Funktionalität legitimer Anwendungen, die auf die Integration verschiedener Online-Dienste angewiesen sind, einschränken.

    CORS ermöglicht die Kommunikation zwischen unterschiedlichen Ursprüngen, ohne die Sicherheit zu beeinträchtigen, indem es spezielle HTTP-Header verwendet. Es ist nicht auf bestimmte HTTP-Methoden wie GET, POST oder DELETE beschränkt und bietet somit eine vielseitige Lösung für eine Vielzahl von Anwendungen. Im Gegensatz zu JSONP, das nur GET-Anfragen unterstützt, ist CORS flexibler und sicherer.

    Die Notwendigkeit von CORS entsteht hauptsächlich, wenn eine Webanwendung auf Ressourcen oder Dienste zugreifen muss, die auf einem anderen Ursprung als dem eigenen gehostet werden. Ein typisches Beispiel hierfür ist eine Benutzeroberfläche (UI), die unter einer Domain wie 'www.beispiel-ui.com' läuft und auf eine API unter einer anderen Domain wie 'api.beispiel-service.com' zugreifen muss.

    Es ist wichtig zu erwähnen, dass CORS nicht für alle Arten von Cross-Origin-Anfragen benötigt wird. Browser erlauben beispielsweise das Laden von JavaScript oder CSS von einem CDN oder das Einbinden von iFrames von anderen Webseiten, solange diese Anfragen die GET-Methode verwenden. Für komplexere Interaktionen, insbesondere solche, die POST- oder PUT-Anfragen beinhalten oder Benutzerdaten und authentifizierte Sessions verarbeiten, wird CORS jedoch unerlässlich.

    In der Praxis müssen Entwickler:innen verstehen, wann und warum CORS in ihren Anwendungen erforderlich ist. Es ist ein Balanceakt zwischen der Sicherheit und der Funktionalität der Anwendung. CORS ermöglicht es, diese Interaktionen auf eine sichere und kontrollierte Weise zu handhaben, was es zu einem unverzichtbaren Werkzeug in der modernen Webentwicklung macht.

    CORS Konfiguration auf dem Server

    Die Implementierung von CORS ist primär eine serverseitige Aufgabe. Während die Client-Seite, zum Beispiel die Benutzeroberfläche einer Webanwendung, CORS-Anfragen initiieren kann, liegt es am Server, diese Anfragen entsprechend den definierten CORS-Richtlinien zu akzeptieren oder abzulehnen. Diese Herangehensweise stellt sicher, dass die Kontrolle über die Zugriffsrechte bei der vertrauenswürdigen Seite – dem Server – liegt.

    Die Konfiguration von CORS auf dem Server beinhaltet in der Regel die Definition spezifischer HTTP-Header, die festlegen, welche Ursprünge Zugriff auf die Ressourcen des Servers haben dürfen. Der wohl bekannteste dieser Header ist Access-Control-Allow-Origin, der entweder einen spezifischen Ursprung oder ein Wildcard-Symbol (*) enthalten kann. Während die Verwendung eines Wildcards für öffentlich zugängliche Ressourcen wie Schriftarten oder Bibliotheken aus einem CDN sinnvoll sein kann, ist es für sensible Daten empfehlenswert, spezifische Ursprünge anzugeben, um die Sicherheit zu erhöhen.

    Ein wichtiger Aspekt bei der Konfiguration von CORS ist, dass alle modernen Browser CORS-Regeln unterstützen und durchsetzen. Sogar ältere Browser wie der Internet Explorer 11 bieten Unterstützung für CORS.

    Im Falle eines Cross-Origin-Zugriffs sendet der Browser automatisch einen 'Origin-Header' mit der Anfrage. Dieser Header informiert den Server über den Ursprung der Anfrage. Der Server prüft dann diesen Ursprung gegen seine CORS-Richtlinien und entscheidet, ob die Anfrage akzeptiert oder abgelehnt wird. Wird die Anfrage akzeptiert, fügt der Server den entsprechenden Access-Control-Allow-Origin-Header zur Antwort hinzu. Dieser Prozess ist transparent und wird vollständig vom Browser und dem Server gehandhabt, sodass die Client-Anwendung in der Regel keine zusätzliche Konfiguration benötigt.

    Methoden in CORS (Simple Requests & Preflight-Requests)

    Simple Requests

    Simple Requests in CORS sind Anfragen, die bestimmte Kriterien erfüllen und daher keiner vorherigen Genehmigung durch den Server bedürfen. Diese Anfragen verwenden üblicherweise Methoden wie GET, HEAD oder POST. Eine wichtige Bedingung für Simple Requests ist, dass der Content-Type der Anfrage entweder application/x-www-form-urlencoded, multipart/form-data oder text/plain sein muss.

    Bei einem Simple Request fügt der Browser automatisch den Origin-Header hinzu. Der Server prüft diesen Origin und, wenn er mit den CORS-Richtlinien des Servers übereinstimmt, antwortet er mit dem Access-Control-Allow-Origin-Header in seiner Antwort. Diese Vorgehensweise ermöglicht eine einfache und effiziente Abwicklung von Cross-Origin-Anfragen für alltägliche Szenarien.

    In Node.js können Simple Requests mit dem CORS-Paket und Express wie folgt implementiert werden:

    1const express = require('express'); 2const cors = require('cors'); 3 4const app = express(); 5 6// Einfache CORS-Konfiguration: Erlaubt Zugriffe von allen Ursprüngen 7app.use(cors()); 8 9app.get('/simple-route', (req, res) => { 10 res.json({ msg: 'Simple Request erfolgreich' }); 11}); 12 13app.listen(3000, () => { 14 console.log('Server läuft auf Port 3000'); 15});

    Dadurch das wir hier keine Optionen festlegen wird automatisch ein Wildcard gesetzt Access-Control-Allow-Origin: * also alle Clienten haben zugriff.

    Unser Beispiel oben erlaubt ja alle externen Anfragen, Simple Requests erlaubt aber auch die Spezifiierung was erlaubt ist:

    • HTTP-Methoden: GET, HEAD, POST.
    • Nur bestimmte Standard-Header wie Accept, Accept-Language, Content-Language, Content-Type (aber nur mit bestimmten Werten wie application/x-www-form-urlencoded, multipart/form-data, text/plain) sind erlaubt.
    • Keine zusätzlichen manuellen HTTP-Header.

    Hier ist ein Beispiel, wie ein Simple Request mit spezifischem Origin Header in Node.js aussehen könnte:

    1const express = require('express'); 2const cors = require('cors'); 3 4const app = express(); 5 6// CORS für Simple Requests mit spezifischem Origin 7app.use(cors({ 8 origin: 'http://example.com' 9})); 10 11app.get('/simple-route', (req, res) => { 12 res.json({ msg: 'Simple Request mit spezifischem Origin' }); 13}); 14 15app.listen(3000, () => { 16 console.log('Server läuft auf Port 3000'); 17});

    Preflight-Requests

    Preflight-Requests sind für komplexere Anfragen vorgesehen, die nicht den Kriterien für Simple Requests entsprechen. Dazu zählen Anfragen, die andere HTTP-Methoden als GET, HEAD oder POST verwenden oder Anfragen, die einen anderen Content-Type als die für Simple Requests zugelassenen haben. Ein Preflight-Request wird mit der HTTP-Methode OPTIONS vor der eigentlichen Anfrage gesendet und enthält Header wie Access-Control-Request-Method und Access-Control-Request-Headers, die dem Server mitteilen, welche Art von Anfrage folgen wird.

    Der Server antwortet auf den Preflight-Request mit Headern, die angeben, ob die eigentliche Anfrage zulässig ist. Diese Antwort kann Header wie Access-Control-Allow-Methods und Access-Control-Allow-Headers enthalten, die spezifizieren, welche Methoden und Header in der tatsächlichen Anfrage verwendet werden dürfen.

    Wenn die Anfragen über die Kriterien von Simple Requests hinausgehen was folgende Punkte einschließt:

    • Verwendung anderer HTTP-Methoden als GET, HEAD, POST. -Hinzufügen von nicht standardmäßigen HTTP-Headern oder Verwendung von Content-Types außerhalb der für Simple Requests erlaubten.
    • Anfragen, die mit Credentials (wie Cookies) gesendet werden.

    Hier ist ein Beispiel für eine CORS-Konfiguration, die einen Preflight-Request benötigen würde:

    1const express = require('express'); 2const cors = require('cors'); 3 4const app = express(); 5 6// Detaillierte CORS-Konfiguration für Preflight-Requests 7const corsOptions = { 8 origin: 'http://example.com', // Spezifischer Ursprung 9 methods: 'POST', // Erlaubte Methoden 10 allowedHeaders: ['Content-Type', 'Authorization'] // Erlaubte Header 11}; 12 13// Aktiviert CORS nur für diese spezielle Route mit den definierten Optionen 14app.options('/complex-route', cors(corsOptions)); // Preflight-Request 15app.post('/complex-route', cors(corsOptions), (req, res) => { 16 res.json({ msg: 'Preflight Request erfolgreich bearbeitet' }); 17}); 18 19app.listen(3000, () => { 20 console.log('Server läuft auf Port 3000'); 21});

    Preflight-Requests Responses

    Die Antwort des Servers auf einen Preflight-Request kann verschiedene Header enthalten, die die Regeln für die folgende eigentliche Anfrage festlegen. Zu diesen Headern gehören Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Expose-Headers, Access-Control-Max-Age, Access-Control-Allow-Methods und Access-Control-Allow-Headers. Diese Header geben an, welche Ursprünge, Methoden und Header für die Cross-Origin-Anfrage erlaubt sind und wie lange die Antwort des Preflight-Requests gültig bleibt.

    Durch die Kombination dieser CORS-Methoden können Entwickler:innen in Node.js aber auch jeder anderen Programmiersprache die Kommunikation zwischen verschiedenen Ursprüngen auf eine sichere und kontrollierte Weise handhaben, was die Entwicklung von robusten und interaktiven Webanwendungen ermöglicht.

    Sources:

    Express CORS Docs

    Wikipedia

    Comments: