So I'm having a bit of an issue where I have two apps hosted on Heroku the first being an Express application with the following cookie settings
const cookieSettings = {
maxAge: expiryTime,
...cookieOptions || {},
// For security these properties should always remain below the spread
httpOnly: true,
secure: process.env.NODE_ENV !== "development",
sameSite: "none",
path: "/",
}
And a Nextjs app which has some middleware that uses the cookie for login control to protect routes.
Locally I have no issues with the following login logic within the login route which sets the cookie browser side
const cookie = getCookie({ tokenOptions: { id: user._id } });
res.setHeader("Set-Cookie", cookie);
return res.sendStatus(200);
I have read there is issues with Heroku as it's on the public list of domains so the browser wont set if it comes from this but the issue I'm having is on a custom domain. My domain is https://www.mydomain.co.uk but for some reason I can't get it to set when I'm on this domain.
When using Postman I do get the cookie back but the domain comes from my API domain ie api.reviewcircle.co.uk which I think is why the browser isn't setting the cookie as the domains don't match but I can't find any info on how to fix this so would be great if anyone has any ideas.
You can see the cookie is included in the response but isn't set:
Related
response cookie screenshot
response header
My site is able to save cookies if the frontend and backend are localhost:3000 talking to localhost:4000, but once I deploy them, it's no longer saving the cookies.
I am using Axios and Express to handle the http requests.
Frontend:
const axiosConfig = axios.create({
baseURL: process.env.REACT_APP_baseURL || "http://localhost:4000" ,
withCredentials: true
});
Backend cookie sending:
const token = jwt.sign(
{
user: existingUser._id,
sciper: input_sciper,
role: role
},
process.env.JWT_SECRET,
{ expiresIn: '1d' }
)
console.log("path /login correct")
res.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "None"
}).send()
Express setup:
app.use(cors({
origin: [
"http://192.168.43.169:3000",
"http://localhost:3000",
"https://epfl-course-rank.herokuapp.com",
"https://www.coursefinder.ch"
],
credentials: true,
}));
The screenshot says that the cookie is sent, as it's in the response header, but not set by chrome (nor any other browser). If I hover over the yellow "!" button, it says "the set cookie is blocked because user preferences."
So if I change my cookie preferences to allow all cookies, my site works -- it still doesn't save cookie into Applications but at least it logs in and sends with requests.
I know there are lots of questions asked on CORS/Cookie setting already, but I am still lost after reading through dozens of them.
I set the credentials, the requests are sent with credentials as well.
It's also really bizarre that when I set my chrome setting to "allow all cookies", then my site works. Does this mean that chrome considers my cookie as third party instead of first-party?
Any help /explanations would be greatly appreciated.
I'm trying to perform a request to a backend service using Axios inside getServerSideProps, but it is failing because the session cookie that is needed for authentication is not included in context.req headers.
The application has its client on a subdomain (e.g. app.domain.com) and the backend service, which is based on Node/Express.js, is on another subdomain (e.g. api.domain.com). The cookie is being sent during authentication and seems to be correctly stored in the browser, but interestingly, it is not stored within the client subdomain, but as part of the backend subdomain (api.domain.com).
I'm using cookie-session for handling the response from Express with the following config flags:
app.use(
cookieSession({
signed: false,
secure: true,
httpOnly: true,
sameSite: "strict"
})
);
I've played with the cookie-session middleware config flags, setting sameSite to "none" and httpOnly to false, with no success. I've also checked the contents of the "context" object in getServerSideProps, just to confirm that the cookie is not being sent to the server.
Assuming you have correctly configured a middleware in your backend to send/accept credentials and accept CORS requests (e.g. https://expressjs.com/en/resources/middleware/cors.html), you can try to add the "domain" flag to cookieSession options.
app.use(
cookieSession({
signed: false,
domain: "your-domain.com",
secure: true,
httpOnly: true,
sameSite: "strict"
})
);
This way, the cookie will be stored in the "domain scope" and not restricted to your backend subdomain.
I am using cookie-session and passportjs to authenticate users in my express app. When I initialize my cookieSession like this:
app.use(cookieSession({
maxAge: 24 * 60 * 60 * 1000,
keys: ['key1']
}));
my cookie is successfully saved to the client. However, the project I am working on requires cross-site requests. Therefore, the secure attribute for the cookie must be set to true and the SameSite attribute must be set to none. In the documentation, these values are able to be set as follows:
app.use(cookieSession({
maxAge: 24 * 60 * 60 * 1000,
secure: true,
sameSite: 'none',
keys: ['key1']
}));
however, when I do this, the cookie fails to save to the client. I'm wondering if anyone knows how to fix this or why this might be happening?
Thank you in advance.
The answer by Luka Darsalia showed me that, in my case at least, the server was refusing to send secure:true cookies to the client, because it thought the client was insecure (due to the request.protocol being http rather than https).
At first I was confused by this, because my address-bar showed https. But then I remembered that the https was only for the connection between my browser and Cloudflare CDN -- the connection between Cloudflare CDN and my actual server was still using http.
Thus, to fix it, I simply had to assure cookie-session (more specifically, this line in the cookies package) that the connection was secure, and thus to go ahead with sending the cookie with the secure:true flag.
To do this, I simply added the following middleware after the cookieSession middleware:
// your existing cookieSession init here
app.use(cookieSession({
[...]
secure: true,
sameSite: "none",
}));
// enable the "secure" flag on the sessionCookies object
app.use((req, res, next)=>{
req["sessionCookies"].secure = true;
next();
});
After authentication use this:
passport.authenticate("local");
req.session.save((error) => {
if (err) {
console.log(err);
} else {
res.redirect("/");
}
});
I am developing a new frontend using Vue to access my existing Laravel 7 app, which uses Sanctum for authentication.
The frontend sits on app.example.com, with the backend being moved to api.example.com. The CORS middleware and Sanctum are properly configured to allow app.example.com, and so far so good.
The GET to /sanctum/csrf-cookie looks fine, however, it doesn't seem to be actually setting the cookies, causing the subsequent request to the API to return a 419.
const config = { withCredentials: true };
const api = process.env.NODE_ENV === 'production' ? 'https://api.example.com' : 'http://localhost:9000';
axios.get(api + '/sanctum/csrf-cookie', config)
.then(() => axios.post(api + '/login', data, config))
.then(response => response.json())
.then(response => { console.log('json', response); });
Console log:
Response headers from /sanctum/csrf-cookie:
No cookies are listed in devtools:
UPDATE 1: Didn't notice this earlier; the warning icons next to each Set-Cookie in the response headers display "This set-cookie's Domain attribute was invalid with respect to the current host url."
Short answer: Ports should not be specified in cookie domain attributes.
Long answer: Laravel Sanctum uses the VerifyCsrfToken middleware to both send and verify the CSRF token, which uses session config values when adding the cookie to the response:
protected function addCookieToResponse($request, $response)
{
$config = config('session');
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']),
$config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null
)
);
return $response;
}
In config/session.php:
'domain' => env('SESSION_DOMAIN', null),
In .env:
SESSION_DOMAIN=localhost:8080
Cookies on the same host ARE NOT distinguishable by ports. Because I had specified the port in the cookie domain, the browser had flagged the cookie as having an invalid domain. Removing the port did the trick.
For me to solve the problem I changed my vue cli host which was 127.0.0.1:8080 to localhost:8080 in browser,and within axios the base url is now http://localhost:7000 which is for laravel api.
after that I then set SESSION_DOMAIN=localhost in .env laravel
Context:
I just split up my existing Next.js/Express app using Lerna, separating the Next.js server (serving the front end on localhost:3000) and my express server (serving the API on localhost:4000). As a result, I had to create an axios instance with the default {baseUrl: localhost:4000}, so my relative path calls will continue to work (ie. axios.post('/api/auth/login', {...})). This was all working before, when the server was serving both the API and the Nextjs app.
Problem:
After this switch, my Authentication flow no longer works properly. After logging in as usual, a page refresh will erase any memory of the user session. As far as I can tell, the Cookie is not being set. I cant see it in my dev-tools, and when the app makes a subsequent call, no cookies are present.
When my app mounts, it makes a call to api/me to see if the user is logged in. If they are, it will respond with the user information to hydrate the app with the users info. If they aren't, it wont do anything special. This request to /api/me no longer contains the persistent cookie set in place by the login call. (dev-tools shows the Set-Cookie header being returned as expected from the original login request)
Possibly Useful information:
Depending on how I run my app, (debugging in VSCode vs running both yarn dev commands in terminal) I will get a CORS error. I am using the cors package with no configuration: app.use(cors())
The call made to /api/me when the application mounts looks like this:
// API.js
`export default axios.create({baseURL: 'http://localhost:4000'})`
// app.js
import API from 'API'
API({
method: 'get',
url: '/api/me'
})
.then(r => {
//...
})
I am setting the cookie using this function
function setCookie(res, token, overwrite, sameSite = true) {
res.cookie(cookieName, token, {
httpOnly: true,
sameSite: sameSite,
secure: process.env.NODE_ENV === 'production',
expires: new Date(Date.now() + cookieExpiration),
overwrite: !!overwrite
})
}
Suspicions
It is some cors policy I'm not familiar with
It is because of sameSite (thought it wouldn't matter)