I have 2 separate apps, let's call them Login & Dashboard. Both apps have a UI written in react and an express server.
In my Login app, when I make a POST from my Login UI, it hits the Login Express server to authenticate. Once authenticated, I set a cookie and redirect to my Dashboard url:
res.cookie(cookie.key, cookie.access_token, {
path: '/',
domain: cookie.domain,
httpOnly: true,
maxAge: cookie.rememberExpiry
})
res.redirect(dashboard_url)
However when I use req.cookies in my dashboard app I don't see any cookies.
When I make the POST from my Login UI I do indeed see a network call stating response header:
Set-Cookie: mycookie=cookievalue; Max-Age=28800; Domain=.local.myurl.com; Path=/; Expires=Thu, 03 Nov 2016 19:20:39 GMT; HttpOnly
Note that as of this moment the time is Nov 3 2016, 11:28 GMT so its not an expiry issue.
To test I have edited my hosts file such that login.local.myurl.com & dashboard.local.myurl.com point to localhost.
Is there any reason why the req.cookies is not available in the Dashboard express app??
My problem was 2 fold.
First I needed to set credentials: 'same-origin' on fetch, which is to say that I had to allow cookies to persist on the request library I was using.
Second, because my server and my client are essentially separate, a redirect on the server did not have the intended effect on the client. Hence I could not just res.redirect from the server response. instead I replaced the res.redirect line with res.status(200).send() and in my client code, I simply did window.location.replace('http://dashboardurl.com').
Hope that helps anyone who has this issue in the future.
Related
I have troubles gettting cookies to work in my production environment which uses a Nest.js API Backend on Azure WebServices (with functions). I basically followed this guide: https://dev.to/azure/build-your-first-serverless-app-with-angular-nestjs-and-azure-108h
I also added an angular frontend app, also hosted on Azure. But as far as my testing concerns this does not matter.
I want to add authorization by JWT stored in a cookie. However, the Backend does not add the cookie to the header in production. In development (localhost) everything works like a charm. It works with the local/dev frontend calling the local/dev backend as well as when I use the VSC Rest Client to just call the API.
However in production I won't receive the cookie nor other header I test. I configured CORS in Azure and this kind of looks good as I do get a HTTP200 and not a CORS Error back. Just the header info is missing.
I already read a lot of advice, but none helped. I do set 'width credentials' (also in azure). Do you experts have any advice what to try or what might be the problem?
Thanks
Controller for testing purposes ...
#Get("server-check")
#HttpCode(HttpStatus.OK)
#Header('Set-Cookie', 'cookieName = 12345; secure; SameSite=none"') // "Usin header decorator"
#Header('Access-Control-Expose-Headers', 'set-cookie, authorization') //
async serverCheck(
#Res() response: Response) {
response.cookie('rememberme', '1') // Using express res object.
return response.send('Cookie has been set! :)')
}
Response from Production env (no cookie in application tab)
Screen from Network-Response in Browser
Response from REST call in VSC
HTTP/1.1 200 OK
Connection: close
Date: Thu, 05 May 2022 11:51:27 GMT
Transfer-Encoding: chunked
Request-Context: appId=cid-v1:b8d7a5c0-962f-40ec-b128-b47139939cf4
Cookie has been set! :)
Locally, my cookies are set fine, but on Heroku with a cross-site call, they are not set in Chrome or Safari, the two browsers I've tried so far. I can only think that it is either because of the cross-site call or because of Heroku's proxy setup.
My Set-Cookie header looks like this:
CookieName=cookieValue; Max-Age=864; Domain=.myPurchasedDomain.net; Path=/; Expires=Tue, 21 Dec 2021 22:43:28 GMT; HttpOnly; Secure; SameSite=None
I have set my BE server to trust proxies, and both my FE and BE should only be available via https. I do not have any (visible) CORS issues. I am setting withCredentials: true in my Express BE and credentials: true in my FE axios options. I'm using next.js on my FE in case that might impact anything.
I don't know how to determine why my cookie is not being set. Any ideas of what I should try next or what might be causing the problem?
Edit: My FE axios call looks like this:
axios
.post(
"herokuUrl/login",
{
/* no body */
},
{
headers: {
Authorization: jwtToken, // Variable from elsewhere
},
withCredentials: true,
}
)
I found the answer:
My FE React app was calling a BE Express server at a different domain. Browsers block setting cookies from different domains. To fix, put both behind the same domain.
This question already has answers here:
How to send cookies with node-fetch?
(3 answers)
Why are cookies not sent to the server via getServerSideProps in Next.js?
(1 answer)
Closed 1 year ago.
I am trying to parse a cookie which was set by including the property httpOnly: true on my custom express server
Now in my NextJs app I can fetch the cookie in the server side method getServerSideProps and access the cookie in ctx.req.cookies from there. But when I make a fetch api call (from the server side method) to the custom server, the cookie does not seem to be accessible in the custom server api call.
Here is my express configuration in server.ts:
// Express Configuration
app.set('port', process.env.PORT || 3000)
app.use(cors({ credentials: true, origin: true }))
app.use(cookieParser())
app.use(passport.initialize())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
This is how I set the cookie on custom express server:
res.cookie('rtk', refreshTokenJWT.token, {
httpOnly: true,
})
This is the fetch api call from getServerSideProps method:
const response = await fetch('http://localhost:3000/refresh_token', {
credentials: 'include',
method: 'POST',
})
Perhaps it is because of the fact that I have to make the api call with absolute url, when I try to do fetch('/refresh_token', ...) I get the error: TypeError: Only absolute URLs are supported
I could possibly send the payload from the server side method in the request body and handle it in the custom express server, but this does not seem like the best solution.
pls elp, thanks in advance.
Edit: To clarify, the await fetch() is happening in the server side method getServerSideProps of nextjs.
Sending the cookie as a header in the fetch api call as suggested by #O. Jones works, but I was under the impression that this wouldn't work since I had set httpOnly: true.
You have to, and are allowed to, parse that server response header despite the httponly attribute being present.
Why? Because HttpOnly (owasp link) is only an instruction to the browser that it should enforce that isolation; when your client code is just another node.js process, it's just a response header.
Here's the regurgitation I'm doing in my auth's unit testing that sounds very similar to your needs; I'm always getting exactly two cookies in comma-delimited one liner format, as emitted by my Koa RESTful app (your Express setup could be comma-delimited like this one, or send two Set-Cookies, so you may need a minor alteration if you have the latter case.)
// raw's format is `koa.sess=YA5N/yI1KhKc/qyylgNduj8vK3e2; path=/; expires=Sun, 09 May 2021 05:17:19 GMT; secure; httponly, koa.sess.sig=8x1BraqjAvKryLx1fvgc0DBu5D4; path=/; expires=Sun, 09 May 2021 05:17:19 GMT; secure; httponly`;
const scp = require('set-cookie-parser');
const cookies = scp.parse(scp.splitCookiesString(headers['set-cookie']));
const next_request_headers = {
cookie: cookies.map(cookie => cookie.name + '=' + cookie.value).join('; ')
};
I'm using npm:set-cookie-parser because cookies are jank and I got fed up with handling commas in both expirations and delimiters.
side issue: abs URLs
TypeError: Only absolute URLs are supported
Cookie domain rules are applied after relative URLs are normalized to absolute URLs, so it sounds to me like this side-issue is only about node-style fetches not having the implicit server/path needed for a browser-style relative ajax URL, and not blocking on anything to do with cookies.
getServerSideProps, by it's name, means it's generated server side
When you do a fetch in getServerSideProps, you do not have any default cookies.
I have a Vue.js project deployed on firebase and a node-express app deployed on Heroku. Now I want to send cookies along with each request to the server using Axios. I am using Axios and cookies are being set using vue-cookies (which are of sameSite: none and secure: true attributes).
In localhost, I can see the cookies in each request in my backend and can access them using req.cookies.session. (The session is my cookie name that is saved on the client-side.)
But in production, I can't see the cookies in the request. What am I doing wrong?
node-express cors
app.use(cors({
credentials: true,
origin: 'https://paid-kickstartu-webapp.web.app',
'Access-Control-Allow-Origin': '*',
}));
Also attaching my screenshots of both Axios configuration and node-express backend for more understanding. Everything is working but cookies are not being sent in the backend from the frontend. In localhost both work as required.
Try this
If you are using Firebase Hosting + Cloud Functions, __session is the only cookie you can store, by design. This is necessary for us to be able to efficiently cache content on the CDN -- we strip all cookies from the request other than __session. This should be documented but doesn't appear to be (oops!). We'll update documentation to reflect this limitation.
Also, you need to set Cache-Control Header as private
res.setHeader('Cache-Control', 'private');
Thank you all for helping. I have solved this problem, what I was doing before was getting the cookie in res body and saving the cookie on the client-side using vue-cookie, So any call to the backend was showing me empty cookies. But now I am setting the cookie header from my backend (node-express) during login and now when I send any further request's I can see the previous cookies that were set in my headers during login.
I have:
1) A client side app that has its own domain: http://client.com
2) A server side app that has a separate domain: http://server.com
Now,
the scenario is:
1) Opening http://client.com/home in the browser, which displays an HTML page.
2) http://client.com/home redirects to http://server.com/login
3) http://server.com/login stores a cookie 'auth' and sends a redirect instruction to http://client.com/welcome
Response:
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 104
Content-Type: text/html; charset=utf-8
Date: Wed, 16 Jan 2019 10:47:11 GMT
Location: http://client.com/welcome
Set-Cookie: auth=1479da80-197c-11e9-ba74-59606594e2fb; Path=/
Vary: Accept
X-Powered-By: Express
4) The browser receives the response, which does contain the cookie 'auth'
5) The browser redirects itself to http://client.com/welcome
6) 'auth' cookie is sent to http://client.com/welcome
Request:
Cookie: auth=1479da80-197c-11e9-ba74-59606594e2fb
7) http://client.com/welcome returns HTML but does not return the cookie 'auth'
8) http://client.com/welcome makes an AJAX request to http://server.com/data (CORS enabled), but the cookie 'auth' is not sent
9) http://server.com/data doesn't recognize the user because there is no cookie
The client side is an angular app hosted by Node.js
Edit:
As suggested, I've added to the response of server.com:
Access-Control-Allow-Credentials: true
but nothing has been changed.
Relevant client side code:
const headerOptions = new HttpHeaders({
'Content-Type': 'application/json', 'withCredentials': 'true', 'Access-Control-Allow-Origin': 'true', 'Access-Control-Allow-Credentials': 'true'
});
this.httpClient.get<any>(this.baseUrl + "data", { headers: headerOptions }).subscribe((res) => {
You should use the withCredentials option when sending your ajax request to your http://server.com and your server.com should have the Access-Control-Allow-Credentials set to true.
Example code in Node.JS server:
var cors = require('cors');
var corsOptions = {
origin: '*',
credentials: true };
app.use(cors(corsOptions));
More on this here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
Example code in Angular.JS client
import {RequestOptions, Request, RequestMethod} from '#angular/http';
const options = new RequestOptions({
method: RequestMethod.Post,
url: 'https://google.com',
withCredentials: true
});
More on this here: https://angular.io/api/http/RequestOptions
Also check this out: https://github.com/angular/angular/issues/24283 - it looks like a particular version of Angular had problems with this flag, so unless you're using a more up-to-date version, you might need to set the header explicitly.
The reasoning of this is, unless the server explicitly tells the client "I will accept cookies (set previously on my domain) passed by another domain" - accepting the cookies would be a security issue. More here: https://en.wikipedia.org/wiki/Cross-site_request_forgery
Your description of what is happening does not seem right.
http://server.com/login stores a cookie 'auth' and sends a redirect instruction to http://client.com/welcome
'auth' cookie is sent to http://client.com/welcome
That is not (or at least should not be) what is happening. When the browser requests http://server.com/login and gets back in the response a Set-Cookie header, the cookie is set on and restricted to the server.com domain, even if the response is a redirect. If you are seeing the 'auth' cookie sent to client.com then that is a cookie that client.com previously set.
Anyway, it seems that what you really care about is
http://client.com/welcome makes an AJAX request to http://server.com/data (CORS enabled), but the cookie 'auth' is not sent
There are a bunch of reasons this can happen.
CORS. You mentioned it was CORS enabled, but for the sake of others reading this, you must have the following CORS headers set on server.com
Access-Control-Allow-Origin: http://client.com
Access-Control-Allow-Credentials: true
Note that you cannot get away with using a wildcard for Access-Control-Allow-Origin when you are sending credentials. Also note that the origin has to be an exact match, including scheme (http or https). In practice, what servers generally do is read the Origin header of the request, check it against a white list, and if it allowed, copy the Origin header value from the request to the Access-Control-Allow-Origin header in the response.
You must set xhr.withCredentials = true in your XHR request. (See MDN for more details.)
Then after you have done all that, you have one other hurdle in your way. Because you are on client.com and trying to send a cookie to server.com, the server.com cookie is considered a "third-party" cookie. AFAIK all the major browsers have a setting that blocks third-party cookies for privacy, because they are most often used by trackers to gather marketing data for advertising. I believe most of them block third-party cookies by default, but I am not sure of that. For sure lots of people have set their browsers to block third-party cookies.
So you have to tell your visitors to configure their browser to allow third-party cookies from server.com.
BTW, it is not safe to set a cookie on a redirect to a different domain. While it is allowed under the specification AFAIK, there have been issues with browser support. See, for example, this Chrome bug.
From Access-Control-Allow-Origin spec:
For requests without credentials, the literal value "" can be specified, as a wildcard;*
Try to add specific domain to Access-Control-Allow-Origin field.
I think you should use proxy in angular angular app. For more info check this link: https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/proxy.md