Setzen Sie Cookies für Cross-Origin-Anfragen

97

Wie kann man Cookies über den Ursprung hinweg teilen? Genauer gesagt, wie wird der Set-CookieHeader in Kombination mit dem Header verwendet Access-Control-Allow-Origin?

Hier ist eine Erklärung meiner Situation:

Ich versuche, ein Cookie für eine API zu setzen, die localhost:4000in einer Web-App ausgeführt wird, auf der gehostet wird localhost:3000.

Es scheint, dass ich die richtigen Antwortheader im Browser erhalte, aber leider haben sie keine Auswirkung. Dies sind die Antwortheader:

HTTP / 1.1 200 OK
Zugriffskontrolle-Zulassen-Ursprung: http: // localhost: 3000
Variieren: Ursprung, Akzeptieren-Codierung
Set-Cookie: token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Maximales Alter = 86400; Domain = localhost: 4000; Pfad = /; Läuft ab = Di, 19. September 2017 21:11:36 GMT; HttpOnly
Inhaltstyp: application / json; Zeichensatz = utf-8
Inhaltslänge: 180
ETag: W / "b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Datum: Montag, 18. September 2017, 21:11:36 Uhr GMT
Verbindung: am Leben bleiben

Außerdem kann ich das Cookie unter sehen, Response Cookieswenn ich den Datenverkehr mithilfe der Registerkarte "Netzwerk" der Chrome-Entwicklertools überprüfe. Ich kann jedoch nicht sehen, dass auf der Registerkarte Anwendung unter ein Cookie gesetzt wird Storage/Cookies. Ich sehe keine CORS-Fehler, daher gehe ich davon aus, dass mir etwas anderes fehlt.

Irgendwelche Vorschläge?

Update I:

Ich verwende das Anforderungsmodul in einer React-Redux-App, um eine Anforderung an einen /signinEndpunkt auf dem Server zu senden. Für den Server benutze ich Express.

Express-Server:

res.cookie ('Token', 'xxx-xxx-xxx', {maxAge: 86400000, httpOnly: true, Domain: 'localhost: 3000'})

Anfrage im Browser:

request.post ({uri: '/ signin', json: {userName: 'userOne', Passwort: '123456'}}, (err, response, body) => {
    // Sachen tun
})

Update II:

Ich setze Anforderungs- und Antwortheader jetzt wie verrückt und stelle sicher, dass sie sowohl in der Anforderung als auch in der Antwort vorhanden sind. Unten ist ein Screenshot. Beachten Sie die Header Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methodsund Access-Control-Allow-Origin. Wenn ich mir das Problem ansehe , das ich bei Axios 'Github gefunden habe , habe ich den Eindruck, dass jetzt alle erforderlichen Header gesetzt sind. Trotzdem gibt es kein Glück ...

Geben Sie hier die Bildbeschreibung ein

Pim Heijden
quelle
4
@PimHeijden Schauen Sie sich das an: developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/… Vielleicht ist die Verwendung von withCredentials das, was Sie brauchen?
Kalamarico
2
Ok, Sie verwenden Anfrage und ich denke, dies ist nicht die beste Wahl. Schauen Sie sich diesen Beitrag und die Antwort an. Axios, von denen ich denke, dass sie für Sie nützlich sein könnten. stackoverflow.com/questions/39794895/…
Kalamarico
Vielen Dank! Ich habe nicht bemerkt, dass das requestModul nicht für die Verwendung im Browser vorgesehen ist. Axios scheint bisher einen tollen Job zu machen. Ich erhalte jetzt sowohl den Header: Access-Control-Allow-Credentials:trueals auch Access-Control-Allow-Origin:http://localhost:3000(zur Aktivierung von CORS). Dies scheint richtig, aber der Set-CookieHeader macht nichts ...
Pim Heijden
Gleiches Problem, jedoch direkt mit Axios: stackoverflow.com/q/43002444/488666 . Während dies { withCredentials: true }tatsächlich von Axios Seite verlangt wird, müssen Server-Header ebenfalls sorgfältig überprüft werden (siehe stackoverflow.com/a/48231372/488666 )
Frosty Z
Welche Server-Header?
Pim Heijden

Antworten:

155

Was musst du machen

Gehen Sie wie folgt vor, um das erfolgreiche Empfangen und Senden von Cookies durch eine CORS-Anfrage zu ermöglichen.

Back-End (Server): Setzen Sie den HTTP-Header- Access-Control-Allow-CredentialsWert auf true. Stellen Sie außerdem sicher , dass die HTTP - Header Access-Control-Allow-Originund Access-Control-Allow-Headerseingestellt sind und nicht mit einem Platzhalter* .

Weitere Informationen zum Einstellen von CORS in Express Js finden Sie in den Dokumenten hier

Front-End (Client): Setzen Sie das XMLHttpRequest.withCredentialsFlag auf true. Dies kann je nach verwendeter Request-Response-Bibliothek auf unterschiedliche Weise erreicht werden:

Oder

Vermeiden Sie die Verwendung von CORS in Kombination mit Cookies. Sie können dies mit einem Proxy erreichen.

Wenn Sie aus irgendeinem Grund nicht vermeiden. Die Lösung ist oben.

Es stellte sich heraus, dass Chrome das Cookie nicht setzt, wenn die Domain einen Port enthält. Das Einstellen für localhost(ohne Port) ist kein Problem. Vielen Dank an Erwin für diesen Tipp!

Pim Heijden
quelle
2
Ich denke, Sie haben dieses Problem nur wegen der localhostÜberprüfung hier: stackoverflow.com/a/1188145 und auch dies kann Ihrem Fall helfen ( stackoverflow.com/questions/50966861/… )
Edwin
5
Diese Antwort hat mir sehr geholfen! Es hat lange gedauert, es zu finden. Aber ich denke, die Antwort sollte erwähnen, dass die Einstellung Access-Control-Allow-Originauf eine explizite Domäne nicht nur "*"erforderlich ist. Dann wäre es die perfekte Antwort
e.dan
6
Dies ist eine gute Antwort, und alle Einstellungen für CORS, Header, Backend und Frontend sowie die Vermeidung von Localhost mit Override / etc / hosts lokal mit einer echten Subdomain. Ich sehe immer noch, dass der Postbote ein SET-COOKIE in den Antwort-Headern anzeigt, das Chrome-Debug jedoch nicht Zeigen Sie dies in den Antwort-Headern und auch das Cookie ist nicht in Chrome gesetzt. Irgendwelche anderen Ideen zu überprüfen?
bjm88
1
@ bjm88 Hast du das herausgefunden? Ich bin in genau der gleichen Situation. Das Cookie wird richtig gesetzt, wenn eine Verbindung von localhost: 3010 zu localhost: 5001 hergestellt wird, funktioniert jedoch nicht von localhost: 3010 zu fakeremote: 5001 (was in meiner Hosts-Datei auf 127.0.0.1 verweist). Es ist genau das gleiche, wenn ich meinen Server auf einem realen Server mit einer benutzerdefinierten Domain hoste (Verbindung von localhost: 3010 zu mydomain.com). Ich habe alles getan, was in dieser Antwort empfohlen wird, und ich habe viele andere Dinge ausprobiert.
Form
1
In Angular besteht die erforderliche Änderung auf der Clientseite auch darin, withCredentials hinzuzufügen: true für die an HttpClient.post, .get, options usw. übergebenen Optionen
Marvin
12

Hinweis für Chrome Browser im Jahr 2020 veröffentlicht.

Eine zukünftige Version von Chrome liefert Cookies mit standortübergreifenden Anfragen nur, wenn sie mit SameSite=Noneund gesetzt sind Secure.

Wenn Ihr Backend-Server SameSite = None nicht festlegt, verwendet Chrome standardmäßig SameSite = Lax und verwendet dieses Cookie nicht mit {withCredentials: true} -Anforderungen.

Weitere Informationen unter https://www.chromium.org/updates/same-site .

Firefox- und Edge-Entwickler möchten diese Funktion auch in Zukunft veröffentlichen.

Spezifikation hier zu finden: https://tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8

LennyLip
quelle
2
Für die Bereitstellung von samesite = none und Secure Flag ist HTTPS erforderlich. Wie kann dies in einem lokalen System erreicht werden, in dem HTTPS keine Option ist? können wir irgendwie umgehen?
Nirmal Patel
@nirmalpatel Entfernen Sie einfach den Wert "Lax" in der Chome-Entwicklungskonsole.
LennyLip
3

Damit der Client Cookies aus Cross-Origin-Anfragen lesen kann, müssen Sie über Folgendes verfügen:

  1. Alle Antworten vom Server müssen Folgendes in ihrem Header haben:

    Access-Control-Allow-Credentials: true

  2. Der Client muss alle Anfragen mit withCredentials: trueOption senden

Bei meiner Implementierung mit Angular 7 und Spring Boot habe ich Folgendes erreicht:


Serverseitig:

@CrossOrigin(origins = "http://my-cross-origin-url.com", allowCredentials = "true")
@Controller
@RequestMapping(path = "/something")
public class SomethingController {
  ...
}

Der origins = "http://my-cross-origin-url.com"Teil wird dem Antwortheader Access-Control-Allow-Origin: http://my-cross-origin-url.comjedes Servers hinzugefügt

Der allowCredentials = "true"Teil wird zum Antwortheader Access-Control-Allow-Credentials: truejedes Servers hinzugefügt. Dies ist erforderlich, damit der Client die Cookies lesen kann


Client-Seite:

import { HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from 'rxjs';

@Injectable()
export class CustomHttpInterceptor implements HttpInterceptor {

    constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // send request with credential options in order to be able to read cross-origin cookies
        req = req.clone({ withCredentials: true });

        // return XSRF-TOKEN in each request's header (anti-CSRF security)
        const headerName = 'X-XSRF-TOKEN';
        let token = this.tokenExtractor.getToken() as string;
        if (token !== null && !req.headers.has(headerName)) {
            req = req.clone({ headers: req.headers.set(headerName, token) });
        }
        return next.handle(req);
    }
}

Mit dieser Klasse fügen Sie Ihrer gesamten Anfrage zusätzliches Material hinzu.

Der erste Teil req = req.clone({ withCredentials: true });ist das, was Sie benötigen, um jede Anfrage mit withCredentials: trueOption zu senden . Dies bedeutet praktisch, dass zuerst eine OPTION-Anforderung gesendet wird, sodass Sie Ihre Cookies und das Autorisierungstoken unter ihnen erhalten, bevor Sie die tatsächlichen POST / PUT / DELETE-Anforderungen senden, an die dieses Token angehängt werden muss (im Header) Befehl an den Server, die Anforderung zu überprüfen und auszuführen.

Der zweite Teil behandelt speziell ein Anti-CSRF-Token für alle Anforderungen. Liest es bei Bedarf aus dem Cookie und schreibt es in den Header jeder Anfrage.

Das gewünschte Ergebnis ist ungefähr so:

Antwort Anfrage

Stefanos Kargas
quelle
Was fügt diese Antwort der bestehenden hinzu?
Pim Heijden
1
Eine tatsächliche Implementierung. Der Grund, warum ich mich dazu entschlossen habe, es zu veröffentlichen, ist, dass ich viel Zeit damit verbringe, nach demselben Thema zu suchen und Teile aus verschiedenen Beiträgen zusammenzufügen, um es zu realisieren. Es sollte für jemanden viel einfacher sein, dasselbe zu tun, wenn er diesen Beitrag als Vergleich hat.
Stefanos Kargas
Das Anzeigen der Einstellung allowCredentials = "true"in der @CrossOriginAnmerkung hat mir geholfen.
ponder275
@ Lennylip, der in seiner obigen Antwort erwähnt wurde, zeigt einen Fehler für die gleiche Site und die sichere Flagge an. So erreichen Sie dies mit einem localhost-Server ohne sicheres Flag.
Nirmal Patel
0

Pims Antwort ist sehr hilfreich. In meinem Fall muss ich verwenden

Expires / Max-Age: "Session"

Wenn es sich um eine dateTime handelt, wird der Cookie auch dann nicht an das Backend gesendet, wenn er nicht abgelaufen ist:

Expires / Max-Age: "Thu, 21 May 2020 09:00:34 GMT"

Ich hoffe, es ist hilfreich für zukünftige Menschen, die möglicherweise das gleiche Problem haben.

Hongbo Miao
quelle
0

Aktualisieren Sie für Express Ihre Express-Bibliothek auf 4.17.1die neueste stabile Version. Dann;

In CorsOption: Stellen Sie originzu Ihrer lokalen Host - URL oder Frontend Produktion URL und credentialszum true Beispiel

  const corsOptions = {
    origin: config.get("origin"),
    credentials: true,
  };

Ich setze meinen Ursprung dynamisch mit dem Modul config npm .

Dann in res.cookie:

Für localhost: Sie überhaupt nicht einstellen sameSite und sichere Option benötigen, können Sie festlegen können , httpOnlyum truefür HTTP - Cookie zu Verhindern , dass XSS - Angriff und andere nützliche Optionen je nach Anwendungsfall.

Für Produktionsumgebung, müssen Sie festlegen , sameSiteum nonefür Cross-Origin - Anfrage und securenach true. Denken Sie daran, sameSitefunktioniert nur mit der neuesten Express-Version, da die neueste Chrome-Version nur Cookies setzt https, sodass eine sichere Option erforderlich ist.

So habe ich meine dynamisch gemacht

 res
    .cookie("access_token", token, {
      httpOnly: true,
      sameSite: app.get("env") === "development" ? true : "none",
      secure: app.get("env") === "development" ? false : true,
    })
Abdullah Oladipo
quelle