Setel cookie untuk permintaan lintas sumber

97

Bagaimana cara membagikan cookie lintas asal? Lebih khusus lagi, bagaimana cara menggunakan Set-Cookietajuk yang dikombinasikan dengan tajuk Access-Control-Allow-Origin?

Berikut penjelasan situasi saya:

Saya mencoba menyetel cookie untuk API yang dijalankan localhost:4000di aplikasi web yang dihosting di localhost:3000.

Sepertinya saya menerima header respons yang benar di browser, tetapi sayangnya header tersebut tidak berpengaruh. Ini adalah tajuk tanggapan:

HTTP / 1.1 200 Oke
Access-Control-Allow-Origin: http: // localhost: 3000
Bervariasi: Asal, Terima-Enkode
Set-Cookie: token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Usia Maks = 86400; Domain = localhost: 4000; Jalur = /; Kedaluwarsa = Sel, 19 Sep 2017 21:11:36 GMT; HttpOnly
Jenis Konten: application / json; charset = utf-8
Panjang Konten: 180
ETag: W / "b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Tanggal: Sen, 18 Sep 2017 21:11:36 GMT
Koneksi: tetap hidup

Selain itu, saya dapat melihat cookie di bawah Response Cookiessaat saya memeriksa lalu lintas menggunakan tab Jaringan alat pengembang Chrome. Namun, saya tidak dapat melihat cookie disetel di tab Aplikasi di bawah Storage/Cookies. Saya tidak melihat kesalahan CORS, jadi saya berasumsi saya melewatkan sesuatu yang lain.

Ada saran?

Perbarui I:

Saya menggunakan modul permintaan di aplikasi React-Redux untuk mengeluarkan permintaan ke /signintitik akhir di server. Untuk server saya menggunakan express.

Server ekspres:

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

Permintaan di browser:

request.post ({uri: '/ signin', json: {userName: 'userOne', password: '123456'}}, (err, response, body) => {
    // melakukan sesuatu
})

Perbarui II:

Sekarang saya menyetel header permintaan dan tanggapan seperti orang gila sekarang, memastikan bahwa mereka ada baik dalam permintaan maupun tanggapan. Berikut tangkapan layarnya. Perhatikan header Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methodsdan Access-Control-Allow-Origin. Melihat masalah yang saya temukan di github Axios , saya mendapat kesan bahwa semua header yang diperlukan sekarang telah disetel. Namun, masih belum berhasil ...

masukkan deskripsi gambar di sini

Pim Heijden
sumber
4
@PimHeijden lihat ini: developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/… mungkin penggunaan withCredentials yang Anda butuhkan?
Kalamarico
2
Ok Anda menggunakan permintaan dan saya pikir ini bukan pilihan terbaik, lihat posting ini dan jawabannya, axios saya pikir bisa berguna bagi Anda. stackoverflow.com/questions/39794895/…
Kalamarico
Terima kasih! Saya gagal memperhatikan bahwa requestmodul tersebut tidak dimaksudkan untuk digunakan di browser. Axios tampaknya melakukan pekerjaan dengan baik sejauh ini. Saya sekarang menerima header: Access-Control-Allow-Credentials:truedan Access-Control-Allow-Origin:http://localhost:3000(digunakan untuk mengaktifkan CORS). Ini sepertinya benar tetapi Set-Cookieheader tidak melakukan apa-apa ...
Pim Heijden
Masalah yang sama, tetapi menggunakan Axios secara langsung: stackoverflow.com/q/43002444/488666 . Meskipun { withCredentials: true }memang diperlukan oleh pihak Axios, header server juga harus diperiksa dengan cermat (lihat stackoverflow.com/a/48231372/488666 )
Frosty Z
header server apa?
Pim Heijden

Jawaban:

155

Apa yang kamu butuhkan

Agar berhasil menerima & mengirim cookie melalui permintaan CORS, lakukan hal berikut.

Back-end (server): Setel nilai header HTTP Access-Control-Allow-Credentialske true. Selain itu, pastikan header HTTP Access-Control-Allow-Origindan Access-Control-Allow-Headerssudah disetel, bukan dengan karakter pengganti* .

Untuk info lebih lanjut tentang pengaturan CORS di express js baca dokumen di sini

Front-end (klien): Setel XMLHttpRequest.withCredentialsflag ke true, ini bisa dilakukan dengan cara berbeda bergantung pada library request-response yang digunakan:

Atau

Hindari penggunaan CORS bersamaan dengan cookie. Anda dapat mencapai ini dengan proxy.

Jika Anda karena alasan apapun jangan menghindarinya. Solusinya ada di atas.

Ternyata Chrome tidak akan menyetel cookie jika domain tersebut berisi porta. Mengaturnya untuk localhost(tanpa port) bukanlah masalah. Terima kasih banyak kepada Erwin atas tip ini!

Pim Heijden
sumber
2
Saya pikir Anda memiliki masalah ini hanya karena localhostperiksa ini di sini: stackoverflow.com/a/1188145 dan juga ini dapat membantu kasus Anda ( stackoverflow.com/questions/50966861/… )
Edwin
5
Jawaban ini sangat membantu saya! Butuh waktu lama untuk menemukannya. Tetapi saya pikir jawabannya harus menyebutkan bahwa pengaturan Access-Control-Allow-Originke domain eksplisit, tidak hanya "*"juga diperlukan. Maka itu akan menjadi jawaban yang sempurna
e.dan
6
ini adalah jawaban yang bagus, dan semua pengaturan untuk CORS, header, backend dan front end, dan menghindari localhost dengan override / etc / hosts secara lokal dengan subdomain yang sebenarnya, saya masih melihat tukang pos menunjukkan SET-COOKIE di header respon tetapi chrome debug tidak tunjukkan ini di header tanggapan dan cookie tidak benar-benar disetel di chrome. Ada ide lain untuk diperiksa?
bjm88
1
@ bjm88 Apakah Anda akhirnya memikirkan hal ini? Saya berada dalam situasi yang sama persis. Cookie diatur dengan benar saat menghubungkan dari localhost: 3010 ke localhost: 5001 tetapi tidak berfungsi dari localhost: 3010 ke fakeremote: 5001 (yang menunjuk ke 127.0.0.1 di file host saya). Ini sama persis ketika saya menghosting server saya di server nyata dengan domain khusus (menghubungkan dari localhost: 3010 ke mydomain.com). Saya telah melakukan semua yang direkomendasikan dalam jawaban ini dan saya mencoba banyak hal lainnya.
Formulir
1
Di Angular, perubahan sisi klien yang diperlukan juga menambahkan withCredentials: true ke opsi yang diteruskan ke HttpClient.post, .get, options, dll.
Marvin
12

Catatan untuk Browser Chrome dirilis pada tahun 2020.

Rilis Chrome di masa mendatang hanya akan mengirimkan cookie dengan permintaan lintas situs jika disetel dengan SameSite=Nonedan Secure.

Jadi, jika server backend Anda tidak menyetel SameSite = None, Chrome akan menggunakan SameSite = Lax secara default dan tidak akan menggunakan cookie ini dengan permintaan {withCredentials: true}.

Info lebih lanjut https://www.chromium.org/updates/same-site .

Pengembang Firefox dan Edge juga ingin merilis fitur ini di masa mendatang.

Spec ditemukan di sini: https://tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8

LennyLip
sumber
2
menyediakan samesite = none dan bendera aman memerlukan HTTPS. Bagaimana cara mencapai ini dalam sistem lokal di mana HTTPS bukan merupakan pilihan? bisakah kita melewati entah bagaimana?
nirmal patel
@nirmalpatel Hapus saja nilai "Lax" di konsol dev Chome.
LennyLip
3

Agar klien dapat membaca cookie dari permintaan lintas sumber, Anda harus memiliki:

  1. Semua tanggapan dari server harus memiliki yang berikut di header mereka:

    Access-Control-Allow-Credentials: true

  2. Klien perlu mengirim semua permintaan dengan withCredentials: trueopsi

Dalam implementasi saya dengan Angular 7 dan Spring Boot, saya mencapainya dengan yang berikut:


Sisi server:

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

Bagian tersebut origins = "http://my-cross-origin-url.com"akan ditambahkan Access-Control-Allow-Origin: http://my-cross-origin-url.comke header respons setiap server

Bagian tersebut allowCredentials = "true"akan ditambahkan Access-Control-Allow-Credentials: trueke header respons setiap server, yang kami butuhkan agar klien dapat membaca cookie


Sisi klien:

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);
    }
}

Dengan kelas ini Anda benar-benar memasukkan barang tambahan ke semua permintaan Anda.

Bagian pertama req = req.clone({ withCredentials: true });, adalah apa yang Anda butuhkan untuk mengirim setiap permintaan dengan withCredentials: trueopsi. Ini secara praktis berarti bahwa permintaan OPSI akan dikirim terlebih dahulu, sehingga Anda mendapatkan cookie dan token otorisasi di antara mereka, sebelum mengirim permintaan POST / PUT / DELETE yang sebenarnya, yang membutuhkan token ini dilampirkan padanya (di header), di perintah agar server memverifikasi dan menjalankan permintaan.

Bagian kedua adalah bagian yang secara khusus menangani token anti-CSRF untuk semua permintaan. Membacanya dari cookie saat diperlukan dan menulisnya di header setiap permintaan.

Hasil yang diinginkan adalah seperti ini:

tanggapan permintaan

Stefanos Kargas
sumber
apa yang ditambahkan jawaban ini ke jawaban yang sudah ada?
Pim Heijden
1
Implementasi yang sebenarnya. Alasan saya memutuskan untuk mempostingnya, adalah karena saya menghabiskan banyak waktu untuk mencari masalah yang sama dan menambahkan potongan dari berbagai posting untuk mewujudkannya. Akan lebih mudah bagi seseorang untuk melakukan hal yang sama, dengan memiliki posting ini sebagai perbandingan.
Stefanos Kargas
Menampilkan pengaturan allowCredentials = "true"di @CrossOriginanotasi membantu saya.
renungkan 275
@ lennylip yang disebutkan dalam jawabannya di atas, menunjukkan error samesite dan secure flag. Cara mencapainya dengan server localhost tanpa tanda aman.
nirmal patel
0

Jawaban Pim sangat membantu. Dalam kasus saya, saya harus menggunakan

Expires / Max-Age: "Session"

Jika ini adalah dateTime, meskipun tidak kedaluwarsa, tetap tidak akan mengirim cookie ke backend:

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

Semoga bermanfaat bagi orang-orang di masa depan yang mungkin menghadapi masalah yang sama.

Hongbo Miao
sumber
0

Untuk ekspres, tingkatkan perpustakaan ekspres Anda 4.17.1yang merupakan versi stabil terbaru. Kemudian;

Dalam CorsOption: Set originke url localhost atau url produksi frontend dan credentialsuntuk true misalnya

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

Saya mengatur asal saya secara dinamis menggunakan modul config npm .

Kemudian, di res.cookie:

Untuk localhost: Anda tidak perlu menyetel samaSite dan opsi aman sama sekali, Anda dapat menyetel httpOnlyke trueuntuk http cookie untuk mencegah serangan XSS dan opsi berguna lainnya tergantung pada kasus penggunaan Anda.

Untuk lingkungan produksi, Anda perlu menyetel sameSiteke noneuntuk permintaan lintas sumber dan secureke true. Ingat sameSitebekerja dengan versi ekspres terbaru hanya seperti saat ini dan versi chrome terbaru hanya mengatur cookie di atas https, sehingga perlu opsi aman.

Inilah cara saya membuat milik saya dinamis

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