Set cookies for cross origin requests
Asked Answered
S

15

269

How to share cookies cross origin? More specifically, how to use the Set-Cookie header in combination with the header Access-Control-Allow-Origin?

Here's an explanation of my situation:

I am attempting to set a cookie for an API that is running on localhost:4000 in a web app that is hosted on localhost:3000.

It seems I'm receiving the right response headers in the browser, but unfortunately they have no effect. These are the response headers:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin, Accept-Encoding
Set-Cookie: token=0d522ba17e130d6d19eb9c25b7ac58387b798639f81ffe75bd449afbc3cc715d6b038e426adeac3316f0511dc7fae3f7; Max-Age=86400; Domain=localhost:4000; Path=/; Expires=Tue, 19 Sep 2017 21:11:36 GMT; HttpOnly
Content-Type: application/json; charset=utf-8
Content-Length: 180
ETag: W/"b4-VNrmF4xNeHGeLrGehNZTQNwAaUQ"
Date: Mon, 18 Sep 2017 21:11:36 GMT
Connection: keep-alive

Furthermore, I can see the cookie under Response Cookies when I inspect the traffic using the Network tab of Chrome's developer tools. Yet, I can't see a cookie being set in in the Application tab under Storage/Cookies. I don't see any CORS errors, so I assume I'm missing something else.

Any suggestions?

Update I:

I'm using the request module in a React-Redux app to issue a request to a /signin endpoint on the server. For the server I use express.

Express server:

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

Request in browser:

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

Update II:

I am setting request and response headers now like crazy now, making sure that they are present in both the request and the response. Below is a screenshot. Notice the headers Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Methods and Access-Control-Allow-Origin. Looking at the issue I found at Axios's github, I'm under the impression that all required headers are now set. Yet, there's still no luck...

enter image description here

Spinel answered 18/9, 2017 at 21:23 Comment(8)
@PimHeijden take a look to this: developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/… maybe the use of withCredentials is what you need?Mallen
Ok you are using request and i think this is not the best choice, take a look to this post and the answer, axios i think could be usefull to you. #39795395Mallen
Thanks! I failed to notice that the request module is not meant for use in the browser. Axios seems to do a great job so far. I receive now both the header: Access-Control-Allow-Credentials:true and Access-Control-Allow-Origin:http://localhost:3000 (used to enable CORS). This seems right but the Set-Cookie header doesnt do anything...Spinel
Same issue, but using directly Axios : https://mcmap.net/q/76159/-make-axios-send-cookies-in-its-requests-automatically/488666. While { withCredentials: true } is indeed required by Axios side, server headers have to be checked carefully as well (see https://mcmap.net/q/76159/-make-axios-send-cookies-in-its-requests-automatically)Pothole
what server headers?Spinel
@PimHeijden any luck? Faced with similar problem. How did you solve it, please.Wassail
@Wassail please read the accepted answerSpinel
I'm having the same issue here but with https protocol. Please check it out here: #76149072Yclept
S
424

Cross site approach

To allow receiving & sending cookies by a CORS request successfully, do the following.

Back-end (server) HTTP header settings:

For more info on setting CORS in express js read the docs here.

Cookie settings: Cookie settings per Chrome and Firefox update in 2021:

  • SameSite=None
  • Secure

When doing SameSite=None, setting Secure is a requirement. See docs on SameSite and on requirement of Secure. Also note that Chrome devtools now have improved filtering and highlighting of problems with cookies in the Network tab and Application tab.

Front-end (client): Set the XMLHttpRequest.withCredentials flag to true, this can be achieved in different ways depending on the request-response library used:

  • ES6 fetch() This is the preferred method for HTTP. Use credentials: 'include'.

  • jQuery 1.5.1 Mentioned for legacy purposes. Use xhrFields: { withCredentials: true }.

  • axios As an example of a popular NPM library. Use withCredentials: true.

Proxy approach

Avoid having to do cross site (CORS) stuff altogether. You can achieve this with a proxy. Simply send all traffic to the same top level domain name and route using DNS (subdomain) and/or load balancing. With Nginx this is relatively little effort.

This approach is a perfect marriage with JAMStack. JAMStack dictates API and Webapp code to be completely decoupled by design. More and more users block 3rd party cookies. If API and Webapp can easily be served on the same host, the 3rd party problem (cross site / CORS) dissolves. Read about JAMStack here or here.

Sidenote

It turned out that Chrome won't set the cookie if the domain contains a port. Setting it for localhost (without port) is not a problem. Many thanks to Erwin for this tip!

Spinel answered 25/9, 2017 at 19:32 Comment(50)
I think you have this problem just because of the localhost check this here: stackoverflow.com/a/1188145 and also this may help your case (#50967361)Wystand
So you're refering to the remark that "the cookie domain must be omitted entirely"?Spinel
This answer helped me so much! Took a long time to find it. But I think the answer should mention that setting Access-Control-Allow-Origin to an explicit domain, not just "*" is also required. Then it would be the perfect answerInterdental
Thanks. Must that header be specific though? I'd like to assume a wildcard also works.Spinel
this is good answer, and all setup for CORS, headers, backend and front end, and avoiding localhost with override /etc/hosts locally with a real subdomain, still I see postman shows a SET-COOKIE in response headers but chrome debug does not show this in response headers and also the cookie isn't actually set in chrome. Any other ideas to check?Porphyroid
@Porphyroid Did you end up figuring this out? I'm in the exact same situation. The cookie is set properly when connecting from localhost:3010 to localhost:5001 but does not work from localhost:3010 to fakeremote:5001 (which points to 127.0.0.1 in my hosts file). It's the exact same when I host my server on a real server with a custom domain (connecting from localhost:3010 to mydomain.com). I've done all that's recommended in this answer and I tried lots of other things.Mythical
I don't see how those headers could be different in Postman vs. Chrome. It's the hostname that counts for the cookie — localhost vs. fakeremote.Spinel
@Mythical have spent a long time searching this; your experience is the same as mine - that the same hostname (different port) will send cookies, but different hostname + port will not. This seems to run counter to the withCredentials spec. Did you make any progress?!Raneeraney
Yes, thank you, the answer is here. I don't fully understand your question, but I'll try to answer it quickly. Cookies don't respect ports. Just leave out the port from the cookie domain. Port is not part of the domain concept anyway. In case of truly different domains, use a proxy if you can. That way you completely avoid the CORS problem to begin with.Spinel
@Raneeraney Unfortunately I don't remember... but I did make it work in the end. I should have gone back to this answer as soon as I found the solution. Anyway, I know I had issues with OPTIONS pre-flight requests at the same time, which required modifications on my server. You might want to make sure everything is OK on that front!Mythical
In Angular, the required client side change is also to add withCredentials: true to the options passed to HttpClient.post, .get, options, etc.Ku
The CORS header Access-Control-Allow-Credentials and the JS-API props "withCredentials/credentials" define if cookies are received/sent by requests/responses and the read/write access to cookies is still handled by the browser's same-origin policy. See also the post here: stackoverflow.com/a/46464563Telephoto
In my case, I had to add the Access-Control-Expose-Headers header to give the client access to the Set-Cookie header. Here's a more in-depth description of what led me to that: https://mcmap.net/q/76163/-not-receiving-set-cookie-header-with-axios-post-request .. Maybe I've misunderstood something -- but if not, hopefully it helps someone :)Arthromere
@lennylip mentioned in his answer below, it is showing error for samesite and secure flag. Now secure flag will only work if we are operating over https. How to achieve this on local setup ?Spireme
Not entirely sure but it is starting to sound a lot like there is no way to achieve this anymore and we all have to start using proxies.Spinel
@PimHeijden if we use Allow-origin to * then i am getting this error has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.Botnick
Yes, that's expected. The 'Access-Control-Allow-Origin' header should not be a wildcard for 'Access-Control-Allow-Credentials: true' (credentials mode 'include' ) to work. Replace the wildcard with a specific hostSpinel
I had success using the following Access-Control-Allow-Headers with value set to Accept,Accept-Language,Content-Language,Content-Type,Authorization,Cookie,X-Requested-With,Origin,Host (Maybe some are in excess, but it worked this way)Triclinium
@Mythical we are facing the same issue. A cookie is not sent for other domains. Were you able to find the solution?Niobous
@Niobous Actually as we were using the cookie for authentication purposes, we skirted the issue completely by using Basic Auth headers on all API requests instead (you can also use token-based auth with a "login" step if you want). In retrospect, that's a better design for an API anyway. We could only do that because we are in full control of the server itself, though. I suppose the "hope the browser sends the cross-origin cookie" approach is fraught with peril as cookies are under assault by tracking protection in modern browsers.Mythical
Hey, great answer! Help me save a lot of trouble while writing my app. May I suggest adding information if you are in development you should set secure to false in order to see cookie in browser? Later in production it should be changed to true of courseScammon
Yes sounds good, can you suggest an edit?Spinel
Unfortunatelly I cannot, edit queue is fullScammon
and also I don't have enough reputation to suggest an edit :)Scammon
But wait a minute @marcin When doing SameSite=None, Secure is even required. The answer states this. By the way, Secure can't be false. It is either set or it isn't set. I suspect your problem is with the SameSite=None approach. You should be using a proxy, much simpler from the browser perspective. Standards around SameSite have recently changed and you can read about here: developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/… I intend to update my answer with the most recent standard but haven't got a lot of time for it.Spinel
Yes, I totally agree with you, when using SameSite=None you should be using secure flag, it is required when you read docs. However I spoke with couple of guys and in development without using proxy approach ( which I tried to avoid ) setting Secure flag to true just won't save cookie in browser, instead you can change it to Secure: false and cookie appears in browser. Later when you post your app to server so everybody can use it you should change to true for security reasons :) Cheers!Scammon
Correct me if I'm wrong but as a consequence of standard change, new versions of Chrome do not work like that anymore. Why? "SameSite=None" only results in cookies being send cross site when "Secure". Such are the default settings of Chrome nowadays. Hence the line in my answer: "When doing SameSite=None, Secure is even required." Ill change the line where it says it is recommended.Spinel
I'm using latest version of chrome, and when I had SameSite: None with Secure:true cookie did not appear. When I changed it to Secure:false I have finally received cookie from my nodejs serverScammon
@Pim What I wrote is based on my experience in displaying cookies in browser :) cheers!Scammon
Hi @Scammon Setting a cookie ("Set-Cookie") is one thing. Sending the cookie back the server is another. Was your cookie also sent ("Cookie") back to the server?Spinel
I've tried only to send from server to display in broswer while logging in.Scammon
In next couple of days I'll probably start to send my cookie back to server to detect if user is logged in etcScammon
If you use subdomain for BE and FE, you should set domain for Cookie to ".example.com" (root domain) for subdomain service can access cookies that other subdomains created.Bezel
I wrote this code in server.js: app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, multipart/form-data"); res.setHeader("Access-Control-Allow-Origin", "localhost:5000"); res.setHeader("Access-Control-Allow-Credentials", true); res.cookie("cookie" , "value" , {sameSite: 'none' , secure : true}) next(); }); Still didn't work out for me . I also tried res.setHeader instead of res.cookie butdidn't worked . Note : My front-end at port 3000 and back end at port 5000Medina
When I see my cookies in application tab they are all set to sameSite = "Lax" evenn after writing this code .Medina
@Scammon Hello, did you end up getting everything to work?Lathy
@PimHeijden Hello, did you ever come to a conclusion as to how to resolve this?Lathy
hey @TheVisionary. I actually did, I followed Pim's answer, however I did set the Secure flag to be false, and this was the only situation when in development in order to "see" the cookie in the browserScammon
@TheVisionary this is the way I did it. pastebin.com/yYJ4LrW9Scammon
@Scammon Is this being used in a production environment? Seems to be insecure (correct me if I'm wrong though)Lathy
@TheVisionary as I mentioned this is in DEVELOPMENT so you can "see" cookie in the browser and make sure everything works fine. When in production you should always set Secure flag to true.Scammon
@TheVisionary and of course you are right about it :)Scammon
I use an Apache server locally at port 81 (using a virtual host). No cookies are coming in with CORS, not with chrome, not with edge.Kook
that must be frustratingSpinel
This answer has multiple issues. 1) Cookie SameSite attribute only applies to sending cookies in CORS request, not setting cookies in CORS response. 2) For sending cookies in CORS request, the server does not need to set Access-Control-Allow-Headers, only Access-Control-Allow-Origin and Access-Control-Allow-Credentials is enough.Inulin
Hi @FishMonitor, Thanks for the comments. Sounds like you could be right. Can you suggest an update by simply editing the answer? I'll review itSpinel
The proxy thing helped me. My cookie was not getting saved into the storage , after i set proxy i could use both strict or lax and didn't need https which was necesasary for none-secureDigitize
I'm having the same issue here but with https protocol. Please check it out here: #76149072Yclept
Cool thing is that create-react-app has built-in support for proxying backend, create-react-app.dev/docs/proxying-api-requests-in-developmentPony
This answer saves my day to send session cookies from the server (.net web api) to the client and vice versa. Both are hosting on different domain (cross-site). Thank you a lot !!!!Understood
H
38

Note for Chrome Browser released in 2020.

A future release of Chrome will only deliver cookies with cross-site requests if they are set with SameSite=None and Secure.

So if your backend server does not set SameSite=None, Chrome will use SameSite=Lax by default and will not use this cookie with { withCredentials: true } requests.

More info https://www.chromium.org/updates/same-site.

Firefox and Edge developers also want to release this feature in the future.

Spec found here: https://datatracker.ietf.org/doc/html/draft-west-cookie-incrementalism-01#page-8

Hellenist answered 4/7, 2020 at 8:11 Comment(5)
providing samesite=none and secure flag require HTTPS. How to achieve this in a local system where HTTPS is not an option? can we bypass somehow?Spireme
@nirmalpatel Just remove the "Lax" value in Chome dev console.Hellenist
@Hellenist I think that if you remove the "Lax" value, it actually still defaults to Lax, not None. (According to web.dev/samesite-cookies-explained, and also MDN.)Sext
@nirmalpatel Just a note, you can have HTTPS on a local system with Let's Encrypt certificates. Look them up if they are needed.Institutor
I'm having the same issue here but with https protocol. Please check it out here: #76149072Yclept
D
21

In order for the client to be able to read cookies from cross-origin requests, you need to have:

  1. All responses from the server need to have the following in their header:

    Access-Control-Allow-Credentials: true

  2. The client needs to send all requests with withCredentials: true option

In my implementation with Angular 7 and Spring Boot, I achieved that with the following:


Server-side:

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

The origins = "http://my-cross-origin-url.com" part will add Access-Control-Allow-Origin: http://my-cross-origin-url.com to every server's response header

The allowCredentials = "true" part will add Access-Control-Allow-Credentials: true to every server's response header, which is what we need in order for the client to read the cookies


Client-side:

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

With this class you actually inject additional stuff to all your request.

The first part req = req.clone({ withCredentials: true });, is what you need in order to send each request with withCredentials: true option. This practically means that an OPTION request will be send first, so that you get your cookies and the authorization token among them, before sending the actual POST/PUT/DELETE requests, which need this token attached to them (in the header), in order for the server to verify and execute the request.

The second part is the one that specifically handles an anti-CSRF token for all requests. Reads it from the cookie when needed and writes it in the header of every request.

The desired result is something like this:

response request

Dicky answered 13/6, 2020 at 19:38 Comment(5)
what does this answer add to the existing one?Spinel
An actual implementation. The reason I decided to post it, is that I spend a lot of time searching for the same issue and adding pieces together from various posts to realize it. It should be much easier for someone to do the same, having this post as a comparison.Dicky
Showing setting allowCredentials = "true" in the @CrossOrigin annotation helped me.Backhander
@lennylip mentioned in his answer above, it is showing error for samesite and secure flag. How to achieve that with localhost server without a secure flag.Spireme
just a tip here, angular default csrf interceptor implementation will not work, use this answer provided interceptor will work and it will attached cookie on GET calls too.Eurypterid
D
6

For express, upgrade your express library to 4.17.1 which is the latest stable version. Then;

In CorsOption: Set origin to your localhost url or your frontend production url and credentials to true e.g

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

I set my origin dynamically using config npm module.

Then , in res.cookie:

For localhost: you do not need to set sameSite and secure option at all, you can set httpOnly to true for http cookie to prevent XSS attack and other useful options depending on your use case.

For production environment, you need to set sameSite to none for cross-origin request and secure to true. Remember sameSite works with express latest version only as at now and latest chrome version only set cookie over https, thus the need for secure option.

Here is how I made mine dynamic

 res
    .cookie("access_token", token, {
      httpOnly: true,
      sameSite: app.get("env") === "development" ? true : "none",
      secure: app.get("env") === "development" ? false : true,
    })
Danas answered 20/9, 2020 at 11:53 Comment(1)
For me SameSite not sameSite (with capital S)Formation
S
4

Pim's answer is very helpful. In my case, I have to use

Expires / Max-Age: "Session"

If it is a dateTime, even it is not expired, it still won't send the cookie to the backend:

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

Hope it is helpful for future people who may meet same issue.

Stormystorting answered 15/5, 2020 at 7:40 Comment(1)
For folks using other servers, this is definitely not requiredMoisesmoishe
P
2
  1. frontend

    `await axios.post(`your api`, data,{
        withCredentials:true,
    })
    await axios.get(`your api`,{
            withCredentials:true,
        });`
    
  2. backend

    var  corsOptions  = {
     origin: 'http://localhost:3000', //frontend url
     credentials: true}
    
    
    app.use(cors(corsOptions));
    const token=jwt.sign({_id:user_id},process.env.JWT_SECRET,{expiresIn:"7d"});
    res.cookie("token",token,{httpOnly:true});
    
    
    
    hope it will work.
    
Pietje answered 20/6, 2021 at 19:50 Comment(1)
Code without any explanation are rarely helpful. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please edit your question and explain how it answers the specific question being asked. See How to Answer.Eluviation
C
1

In the latest chrome standard, if CORS requests to bring cookies, it must turn on samesite = none and secure, and the back-end domain name must turn on HTTPS,

Collateral answered 27/8, 2021 at 10:13 Comment(1)
Can't you include cookies for https call from an http site with SameSite=None without SecureSpinel
E
1

After more then a day of trying all your suggestions and many more, I surrender. Chrome just does not accept my cross domain cookies on localhost. No errors, just silently ignored. I want to have http only cookies to safer store a token. So for localhost a proxy sounds like the best way around this. I haven't really tried that.

What I ended up doing, maybe it helps someone.

Backend (node/express/typescript)

set cookie as you normally would

res.status(200).cookie("token", token, cookieOptions)

make a work around for localhost

// if origin localhost
response.setHeader("X-Set-Cookie", response.getHeader("set-cookie") ?? "");

Allow x-set-cookie header in cors

app.use(cors({
    //...
    exposedHeaders: [
        "X-Set-Cookie",
        //... 
    ]
}));

Frontend (Axios)

On the Axios response remove the domain= so it's defaulted. split multiple cookies and store them locally.

// Localhost cookie work around
const xcookies = response.headers?.["x-set-cookie"];
if(xcookies !== undefined){
    xcookies
        .replace(/\s+Domain=[^=\s;]+;/g, "")
        .split(/,\s+(?=[^=\s]+=[^=\s]+)/)
        .forEach((cookie:string) => {
            document.cookie = cookie.trim();
    });
}

Not ideal, but I can move on with my life again.

In general this is just been made to complicated I think :-(

Update my use case maybe we can resolve it?

It's a heroku server with a custom domain. According to this article that should be okay https://devcenter.heroku.com/articles/cookies-and-herokuapp-com

I made an isolated test case but still no joy. I'm pretty sure I've seen it work in FireFox before but currently nothing seems to work, besides my nasty work around.

Server Side

app.set("trust proxy", 1);

app.get("/cors-cookie", (request: Request, response: Response) => {

    // http://localhost:3000
    console.log("origin", request.headers?.["origin"]);

    const headers = response.getHeaders();
    Object.keys(headers).forEach(x => {
        response.removeHeader(x);
        
        console.log("remove header ", x, headers[x]);
    });
    console.log("headers", response.getHeaders());

    const expiryOffset = 1*24*60*60*1000; // +1 day

    const cookieOptions:CookieOptions = {
        path: "/",
        httpOnly: true,
        sameSite: "none",
        secure: true,
        domain: "api.xxxx.nl",
        expires: new Date(Date.now() + expiryOffset)
    }

    return response
        .status(200)
        .header("Access-Control-Allow-Credentials", "true")
        .header("Access-Control-Allow-Origin", "http://localhost:3000")
        .header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT")
        .header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
        .cookie("test-1", "_1_", cookieOptions)
        .cookie("test-2", "_2_", {...cookieOptions, ...{ httpOnly: false }})
        .cookie("test-3", "_3_", {...cookieOptions, ...{ domain: undefined }})
        .cookie("test-4", "_4_", {...cookieOptions, ...{ domain: undefined, httpOnly: false }})
        .cookie("test-5", "_5_", {...cookieOptions, ...{ domain: undefined, sameSite: "lax" }})
        .cookie("test-6", "_6_", {...cookieOptions, ...{ domain: undefined, httpOnly: false, sameSite: "lax" }})
        .cookie("test-7", "_7_", {...cookieOptions, ...{ domain: "localhost"}}) // Invalid domain
        .cookie("test-8", "_8_", {...cookieOptions, ...{ domain: ".localhost"}}) // Invalid domain
        .cookie("test-9", "_9_", {...cookieOptions, ...{ domain: "http://localhost:3000"}}) // Invalid domain
        .json({
            message: "cookie"
        });
});

Client side

const response = await axios("https://api.xxxx.nl/cors-cookie", {
    method: "get",
    withCredentials: true,
    headers: {
        "Accept": "application/json",
        "Content-Type": "application/json",                
    }
});

Which yields the following reponse

enter image description here

I see the cookies in the Network > request > cookies Tab.

But no cookies under Application > Storage > Cookies nor in document.cookie.

Ending answered 1/9, 2021 at 6:18 Comment(5)
I think you can make this work with the regular "Set-Cookie" headerSpinel
I would like to thinks so too but I couldn’t manage. Will post some details maybe someone else can tell me what I’m missing?Ending
The cookieOptions variable is crucial here. What options does the cookie have? Besides this, your browser settings can also interfere dramatically.Spinel
@PimHeijden I've updated my post with an isolated case.Ending
Try looking in the per request cookies sub tab of the network tab. There you get more information about why a cookie was or wasn't set.Spinel
S
1

Seems it is not possible to set cookies in cross origin requests as from app.example.com to api.example.com for all users.

Note that cookies set in CORS responses are subject to normal third-party cookie policies. In the example above, the page is loaded from foo.example but the cookie on line 19 is sent by bar.other, and would thus not be saved if the user's browser is configured to reject all third-party cookies.

Cookie in the request (line 10) may also be suppressed in normal third-party cookie policies. The enforced cookie policy may therefore nullify the capability described in this chapter, effectively preventing you from making credentialed requests whatsoever.

See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#third-party_cookies

Additionally, Google is currently set to phase out third-party cookies in Chrome by 2024. (see google)

Alternativ ideas are

  1. Proxy calls to api.example.com through app.example.com/api/.
  2. Add an iframe to app.example.com that loads a page from api.example.com. The iFrame page can set cookies and send requests to api.example.com. Pages on app.example.com can send messages to the iFrame via postMessage.
  3. Use an iFrame to set a cookie on the parent as example.com. Though I do not see an advantage to 2 as it is also needed to pass data (user credentials in case of a auth cookie) to the iFrame.
  4. Instead of using an iFrame the client browser can be redirected to api.example.com to set a cookie.
  5. Use an alternative to cookies to pass data. For example if an auth cookie is needed, set it on app.example.com and when calling api.example.com, pass the cookie data in a header. Though this is a bit less secure as you will need access to the cookie on the client and thus cannot use HttpOnly.
Solan answered 25/3, 2023 at 5:35 Comment(0)
I
0

Pim's Answer is very helpful, But here is an edge case I had gone through,

In my case even though I had set the Access-Control-Allow-Origin to specific origins in BE , In FE I received it as * ; which was not allowed

The problem was, some other person handled the webserver setup, in that, there was a config to set the Access-Control-* headers which was overriding my headers set from BE application

phew.. took a while to figure it out .

So, if there is mismatches in what you set and what you received, Check your web server configs also.

Impossibly answered 7/9, 2021 at 5:34 Comment(0)
L
0

Hope this would help for me regarding the sameSite property, after enabling CORS I also add "CookieSameSite = SameSiteMode.None" to the CookieAuthenticationOptions in the Startup file

app.UseCookieAuthentication(new CookieAuthenticationOptions {
..... CookieSameSite = SameSiteMode.None, ..... }

Lyonnaise answered 16/12, 2021 at 9:37 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewSacrilege
J
0

This is an answer to "Lode Michels" from above regarding CORS cookie with the Heroku server, (and for other cloud providers, like AWS)

The reason your CORS cookie can't be set is because Heroku strip down SSL certificate at Load Balancer, so when you try to set the "secure" cookie at the server, it fails since it's no longer from the secure connection.

You can explicitally specify if the connection is secure, rather than the cookie module examining request. https://github.com/pillarjs/cookies

with koa, add this:

ctx.cookies.secure = true;

edit: I can't comment on that answer directly due to lower than 50 reputation

Jackquelinejackrabbit answered 18/4, 2022 at 3:52 Comment(0)
F
0

This code worked for me

In the backend

Set credentials to true in your corsOptions:

const corsOptions = {
 credentials: true,
  };

Set cookies before sending requests:

res.cookie('token', 'xxx-xxx-xxx', { 
maxAge: 24*60*60*1000, httpOnly: true, 
SameSite:"None" })

In the frontend

Request in browser (using axios):

axios.post('uri/signin', 
JSON.stringify({ username: 'userOne', 
password: '123456'}),. 
{withCredentials:true})
.the(result 
=>console.log(result?.data))
.catch(err => console.log(err))
Formation answered 9/10, 2022 at 22:18 Comment(0)
T
0

In addition to awesome answers above I'd like to offer alternatives. Generally cross-origin cookies is hard especially if you use it for authentication. So my suggestion would be to use proxy. Basically just a proxy to make your backend and frontend lookslike coming from the same host. You can use reverse proxy from loadbalancer that you're using e.g. Nginx, httpd, etc.

Bonus: If you're using Netlify you can use their rewrites and proxy feature to make it work.

For example, as hobbyist I want to develop things cheaply so I use the free version of Fly.io and Netlify for backend and frontend respectively. So to make everything appears coming from the same host, in the Netlify (frontend) I setup the following to avoid cross origin cookies.

# netlify.toml
[[redirects]]
from = "/api/*"
to = "https://yourbackend.fly.dev/:splat"
status = 200
force = true
headers = {Origin = "https://yourfrontend.netlify.app"}


[[redirects]]
from = "/*"
to = "/index.html"
status = 20

Hope it useful for you 🙂

Triode answered 4/4, 2023 at 21:20 Comment(0)
T
0

Backend code :

app.use(
  cors({
    credentials: true,
    origin: [
      "http://localhost:3000",
      "http://localhost:8080",
      "http://localhost:4200",
    ],
  })
);
router.post("/", (req, res) => {
  res
    .cookie("access_token", "token", { httpOnly: true })
    .status(200)
    .json({ message: "Cookie set!" });
});

Frontend code:

const fetchData = async () => {
  const response = await fetch("http://localhost:4000/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    credentials: "include",
    body: JSON.stringify({
      name: "sreesha",
    }),
  });
  const json = await response.json();
  console.log(json);
};

Dont forget to set credentials:include in frontend . Even if you set cookie in response, without credentials include ,cookie will not be set in browser

Tipcat answered 1/5, 2023 at 19:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.