Como compartilhar cookies de origem cruzada? Mais especificamente, como usar o Set-Cookie
cabeçalho em combinação com o cabeçalho Access-Control-Allow-Origin
?
Aqui está uma explicação da minha situação:
Estou tentando definir um cookie para uma API que está sendo executada localhost:4000
em um aplicativo da web que está hospedado em localhost:3000
.
Parece que estou recebendo os cabeçalhos de resposta corretos no navegador, mas infelizmente eles não surtem efeito. Estes são os cabeçalhos de resposta:
HTTP / 1.1 200 OK Access-Control-Allow-Origin: http: // localhost: 3000 Variar: Origem, Aceitar-Codificação Set-Cookie: token = 0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Max-Age = 86400; Domínio = localhost: 4000; Caminho = /; Expira = terça, 19 de setembro de 2017 21:11:36 GMT; HttpOnly Tipo de conteúdo: aplicativo / json; charset = utf-8 Comprimento do conteúdo: 180 ETag: W / "b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ" Data: Seg, 18 de setembro de 2017 21:11:36 GMT Conexão: keep-alive
Além disso, posso ver o cookie Response Cookies
quando inspeciono o tráfego usando a guia Rede das ferramentas de desenvolvedor do Chrome. Ainda assim, não consigo ver um cookie sendo definido na guia Aplicativo em Storage/Cookies
. Não vejo nenhum erro CORS, então presumo que esteja faltando algo mais.
Alguma sugestão?
Atualização I:
Estou usando o módulo de solicitação em um aplicativo React-Redux para emitir uma solicitação para um /signin
endpoint no servidor. Para o servidor eu uso express.
Servidor expresso:
res.cookie ('token', 'xxx-xxx-xxx', {maxAge: 86400000, httpOnly: true, domain: 'localhost: 3000'})
Solicitação no navegador:
request.post ({uri: '/ signin', json: {userName: 'userOne', senha: '123456'}}, (err, resposta, corpo) => { // fazendo coisas })
Atualização II:
Estou definindo cabeçalhos de solicitação e resposta como um louco, certificando-me de que eles estejam presentes tanto na solicitação quanto na resposta. Abaixo está uma captura de tela. Observe os cabeçalhos Access-Control-Allow-Credentials
, Access-Control-Allow-Headers
, Access-Control-Allow-Methods
e Access-Control-Allow-Origin
. Olhando para o problema que encontrei no github do Axios , tenho a impressão de que todos os cabeçalhos necessários agora estão definidos. No entanto, ainda não há sorte ...
fonte
request
módulo não se destina ao uso no navegador. Axios parece ter feito um ótimo trabalho até agora. Recebo agora o cabeçalho:Access-Control-Allow-Credentials:true
eAccess-Control-Allow-Origin:http://localhost:3000
(usado para habilitar o CORS). Isso parece certo, mas oSet-Cookie
cabeçalho não faz nada ...{ withCredentials: true }
seja de fato exigido pelo lado do Axios, os cabeçalhos do servidor também devem ser verificados com cuidado (consulte stackoverflow.com/a/48231372/488666 )Respostas:
O que você precisa fazer
Para permitir o recebimento e envio de cookies por uma solicitação CORS com sucesso, faça o seguinte.
Back-end (servidor): Defina o
Access-Control-Allow-Credentials
valor do cabeçalho HTTP comotrue
. Além disso, certifique-se de que os cabeçalhos HTTPAccess-Control-Allow-Origin
eAccess-Control-Allow-Headers
estejam definidos e não com um caractere curinga*
.Para obter mais informações sobre como configurar CORS em express js, leia os documentos aqui
Front-end (cliente): defina o
XMLHttpRequest.withCredentials
sinalizador comotrue
, isso pode ser feito de diferentes maneiras, dependendo da biblioteca de solicitação-resposta usada:jQuery 1.5.1
xhrFields: {withCredentials: true}
ES6 fetch ()
credentials: 'include'
axios :
withCredentials: true
Ou
Evite usar CORS em combinação com cookies. Você pode fazer isso com um proxy.
Se você por algum motivo, não evite. A solução está acima.
Descobriu-se que o Chrome não definirá o cookie se o domínio contiver uma porta. Configurá-lo para
localhost
(sem porta) não é um problema. Muito obrigado ao Erwin por esta dica!fonte
localhost
checagem aqui: stackoverflow.com/a/1188145 e também isso pode ajudar no seu caso ( stackoverflow.com/questions/50966861/… )Access-Control-Allow-Origin
de um domínio explícito, não apenas,"*"
também é necessária. Então seria a resposta perfeitaNota para o navegador Chrome lançado em 2020.
Portanto, se seu servidor de back-end não definir SameSite = None, o Chrome usará SameSite = Lax por padrão e não usará este cookie com solicitações {withCredentials: true}.
Mais informações https://www.chromium.org/updates/same-site .
Os desenvolvedores do Firefox e do Edge também desejam lançar esse recurso no futuro.
Especificação encontrada aqui: https://tools.ietf.org/html/draft-west-cookie-incrementalism-01#page-8
fonte
Para que o cliente possa ler cookies de solicitações de origem cruzada, você precisa ter:
Todas as respostas do servidor precisam ter o seguinte em seu cabeçalho:
Access-Control-Allow-Credentials: true
O cliente precisa enviar todas as solicitações com
withCredentials: true
opçãoEm minha implementação com Angular 7 e Spring Boot, consegui isso com o seguinte:
Lado do servidor:
@CrossOrigin(origins = "http://my-cross-origin-url.com", allowCredentials = "true") @Controller @RequestMapping(path = "/something") public class SomethingController { ... }
A
origins = "http://my-cross-origin-url.com"
parte será adicionadaAccess-Control-Allow-Origin: http://my-cross-origin-url.com
ao cabeçalho de resposta de cada servidorA
allowCredentials = "true"
parte será adicionadaAccess-Control-Allow-Credentials: true
ao cabeçalho de resposta de cada servidor, que é o que precisamos para que o cliente leia os cookiesLado do cliente:
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); } }
Com essa classe, você injeta material adicional em todas as suas solicitações.
A primeira parte
req = req.clone({ withCredentials: true });
é o que você precisa para enviar cada solicitação comwithCredentials: true
opção. Isso praticamente significa que uma solicitação OPTION será enviada primeiro, para que você obtenha seus cookies e o token de autorização entre eles, antes de enviar as solicitações POST / PUT / DELETE reais, que precisam deste token anexado a elas (no cabeçalho), em pedido para o servidor verificar e executar a solicitação.A segunda parte é aquela que trata especificamente de um token anti-CSRF para todas as solicitações. Lê-o do cookie quando necessário e o grava no cabeçalho de cada solicitação.
O resultado desejado é mais ou menos assim:
fonte
allowCredentials = "true"
na@CrossOrigin
anotação me ajudou.Para expresso, atualize sua biblioteca expressa para
4.17.1
a versão estável mais recente. Então;Em CorsOption: Defina
origin
como seu localhost url ou seu URL de produção de frontend e,credentials
portrue
exemplo,Eu defino minha origem dinamicamente usando o módulo config npm .
Então, em res.cookie:
Para localhost: você não precisa definir sameSite e opção de seguro em tudo, você pode definir
httpOnly
paratrue
para http cookie para ataque prevent XSS e outras úteis opções , dependendo do seu caso de uso.Para o ambiente de produção, você precisa definir
sameSite
comonone
para solicitação de origem cruzada esecure
comotrue
. Lembre-se desameSite
funcionar com a versão mais recente expressa apenas como agora e a versão mais recente do Chrome configurada apenas com o cookiehttps
, portanto, a necessidade de uma opção segura.Aqui está como tornei o meu dinâmico
fonte
A resposta de Pim é muito útil. No meu caso, tenho que usar
Se for um dateTime, mesmo que não tenha expirado, ele ainda não enviará o cookie para o backend:
Espero que seja útil para futuras pessoas que possam encontrar o mesmo problema.
fonte