Why browser can't set cookies by default - express

I know there are a lot of discussion about this topic already did. and i think i have followed all the instruction but still can't succeed.I think i'm missing something.
Here's my cookie :
const cookieOptions = {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
),
httpOnly: true,
path: '/',
sameSite: 'lax',
maxAge: 1000 * 60 * 60 * 1,
};
if (process.env.NODE_ENV === 'production') {
cookieOptions.secure = true;
}
res.cookie('jwt', token, cookieOptions);
Cors :
app.use(
cors({
origin: ['http://localhost:3000'],
credentials: true,
})
);
Frontend :
const { data } = await axios.post(
"http://127.0.0.1:8000/api/v1/users/login",
{
email: enteredEmail,
password: enteredPassword,
},
{ withCredentials: true }
);
I have also used axios.defaults.withCredentials = true; . But still i can't find my jwt in Application > cookies.
Here's my Response header
and this is my request header

"res.cookie()" only set the HTTP Set-Cookie header with the options provided. Any option not specified defaults to the value stated in RFC 6265.If you take a look closely at the response header, see that jwt=... is present in the Set-Cookie header.
For your implementation, where you try to access the data from the axios response, you should look into res.json() or res.send(), as it directly sends the response back in the body, not the header.

Related

HttpOnly cookie appears in response header but is not being saved to the browser

I recently built a simple real-time chat application with Nextjs on the frontend and Express on the backend. The frontend is deployed on vercel while the backend is deployed on heroku. When a user logs into the app, the backend generates a jwt token which is then sent via an HttpOnly cookie back to the frontend. Here is the code for said response:
const authenticate = async (req, res, next) => {
userService
.authenticate(req)
.then((user) => {
const { token, ...userInfo } = user;
res
.setHeader(
"Set-Cookie",
cookie.serialize("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV !== "development",
maxAge: 60 * 60 * 24,
sameSite: "none",
path: "/",
})
)
.status(200)
.json(userInfo);
})
.catch(next);
};
After authentication, each subsequent request to the backend is supposed to send the token to ensure the user is logged in. For example, this is the request sent to the server to get a chat between the logged in user and another user.
const getChat = async (id) => {
const identification = id;
const response = await axios.get(
`<SERVER_URL>/chats/chat/${identification}`,
{ withCredentials: true }
);
return response;
};
In development when on localhost:3000 for the frontend and localhost:4000 for the backend, everything works fine. However, when I deployed the frontend to vercel and the backend to heroku, the browser simply refuses to save the cookie! The jwt token appears in the response header after sending the authentication request, but it isn't saved to the browser. I have tried absolutely everything I can think of, including changing the cookie parameters, but I can't get it to work. I am pretty sure I have cors properly configured on the backend as well, along with the cookie-parser module:
const cors = require("cors");
const cookieParser = require("cookie-parser");
app.use(
cors({
origin: "<CLIENT_URL>",
credentials: true,
})
app.use(cookieParser());
Thanks for taking the time to read this, any help would be greatly appreciated! And my apologies if I have not elaborated enough, this is my first post here and I'm still trying to learn the proper etiquette of the site!
HttpOnly can not read or write on client-side but when the first HttpOnly send through a request other request on the same origin can access the coockies in backend but you should request in Next.js like this.
Next.js using fetch :
const req = await fetch("http://localhost:7000/api/auth/login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
body: JSON.stringify({
email: formData.get("email"),
password: formData.get("password"),
}),
});
const data = await req.json();
then in backend you can read the coockie through coockie-parser
server.js:
const cookieParser = require("cookie-parser");
app.use(coockieParser());
route.post('/login',(req,res) => {
if(user){
res
.cookie("access_token", newToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production" ? true : false,
})
.status(200)
.json({ ok: true, payload: data });
}
})
Now you can read this cookie in other routes but sure about the expiration time.

NestJS API cookies arent working on Heroku

My NEST api works on localhost but cookies are not working on heroku.
Here is my config
app.enableCors({ origin: process.env.FRONT_END_URL, credentials: true }); // FE_URL == http://localhost:3000 (a react app)
app.set('trust proxy', 1); // I've seen people using express using this, but isn't working here
app.use((req, res, next) => {
req.connection.proxySecure = true; // If i don't do this, it'll throw an error if i'm using secure == true and sameSite == 'none'
next();
});
app.use(
sessions({
cookieName: 'FEATSession',
secret: 'ThisIsMyTopSecretWord',
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5,
cookie: {
path: '/', // Tried '/' and not setting this prop too
// domain: 'feat-be.herokuapp.com', // I tried using and not using it too
sameSite: 'none',
secure: true,
httpOnly: true, // Tried true and false too
},
}),
);
Everything else is working fine, only cookies doesn't.
I've just solved myself a very similar problem today, try different syntax of setting cookie:
SameSite property invalid Cookies HTTPONLY MERN

How to set cookies in Cypress before visiting react app running on localhost

The steps I want to take are:
Start the Cypress test-suite and use cy.setCookie to set the JSESSIONID cookie (already acquired and up-to-date)
After the cookie is set, then use cy.visit to access the running app
The issue:
The cookie is not set before cy.visit runs and this causes the app redirect to an unauthorized page
What I have done so far:
Cypress.Cookies.defaults({
preserve: 'JSESSIONID'
})
cy.setCookie('JSESSIONID', Cypress.env('JSESSIONID'), {
path: '/',
domain: '<domain.name>',
httpOnly: true,
secure: true,
sameSite: 'no_restriction',
log: true,
}).then(() => cy.visit('localhost:3000/<authenticated-route>')
It might be worth mentioning that <domain.name> is of the form www.staging.etc.com whereas is running locally: localhost:3000/
Any help would be greatly appreciated. Thanks in advance
I solved the issue by doing a cy.request to login before using cy.visit.
Code looks something like this:
const login = () => {
const headers = new Headers()
headers.append("Content", "application/x-www-form-urlencoded")
headers.append("Accept-Encoding", "gzip:deflate")
headers.append("Content-Type", "application/x-www-form-urlencoded")
cy.request({
url: Cypress.env("LOGIN_URL"),
method: 'POST',
form: true,
headers,
body: {
"email": Cypress.env("EMAIL"),
"password": Cypress.env("PASSWORD")
}
}).then((response) => {
console.log(response)
console.log(response.body)
setCookie(response.COOKIE)
})
}
export const loginAndStartTests = () => {
login()
cy.visit('/<homepage>')
}
Take a look at Provide an onBeforeLoad callback function
The example recipe mentioned (code is here) is setting a token in local storage, but should apply as well to cookies
cy.visit('http://localhost:3000/#dashboard', {
onBeforeLoad: (contentWindow) => {
cy.setCookie('JSESSIONID', Cypress.env('JSESSIONID'), {
path: '/',
...
})
}
})
I think the problem is that cy.visit() is one of the places where everything is cleared down, but the hook is provided to get around that. Although, I would expect preserve to work as well.
You need to set a cookie from the argument contentWindow:
A cookie setter util:
export const setCookieToContentWindow = (
contentWindow,
name,
value,
{ expireMinutes = 1 } = {},
) => {
const date = new Date();
const expireTime = expireMinutes * 60 * 1000;
date.setTime(date.getTime() + expireTime);
const assignment = `${name}=${encodeURIComponent(value)}`;
const expires = `expires=${date.toGMTString()}`;
const path = 'path=/';
contentWindow.document.cookie = [assignment, expires, path].join(';');
};
Using onBeforeLoad:
cy.visit('http://localhost:3000/#dashboard', {
onBeforeLoad: (contentWindow) => {
setCookieToContentWindow(contentWindow, 'COOKIE_NAME', 'COOKIE_VALUE');
},
});

Cookies from separate api with Next.js

I am currently making a Next.js app and I am having issues with cookies. I have an express API running on localhost:3001 which sets cookies when I signup/signin using express-cookie-session library. Whenever I do it through postman it works fine, however when I do it from next app an api it doesn't have "Set-Cookie" header in the response. I suspect it has to do with next app and express being on different ports and express being unable to set cookies to the next app however I'm unsure what to do about it. If it matters I wanted to set JWT's this way. It's possible to send them in response body but I would like to know how I could do it through the cookies.
Here are some relevant configurations:
app.use(cors());
app.set('trust proxy', true);
...
app.use(cookieSession({ signed: false, secure: false, sameSite: "lax" }));
a sign up controller:
const signUp = async (req: Request, res: Response, next: NextFunction) => {
...
const accessToken = TokenGenerator.generateAccessToken(user);
const refreshToken = TokenGenerator.generateRefreshToken(user);
user.refreshToken = refreshToken;
...
req.session = { accessToken, refreshToken };
res.send(user);
};
and getServerSideProps function
export const getServerSideProps = async (ctx) => {
const headers = ctx.req.headers;
const res = await axios.get("http://localhost:3001/users/current-user", {
headers,
});
return { props: { data: res.data } };
};
EDIT: Set-Cookie header is actually shown in chrome console however it isn't being console logged from axios response.
Here's example of cookie:
Set-Cookie: express:sess=eyJhY2Nlc3NUb2tlbiI6ImV5SmhiR2NpT2lKSVV6STFOaUlz
SW5SNWNDSTZJa3BYVkNKOS5leUpwWkNJNklqVm1abVEyTldSalpURXlaak5pT0RVellUWXlNR0
psT0NJc0ltVnRZV2xzSWpvaWJHdHNiaUlzSW1saGRDSTZNVFl4TURRME1qSXdOQ3dpWlhod0lq
b3hOakV3TkRReU1qRTVmUS5NN2szX1BVQy1hbzRQb2w4OXNiS05ndS1ndkpqNEVfUWdoX2RHSU
ZrZlZFIiwicmVmcmVzaFRva2VuIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhW
Q0o5LmV5SnBaQ0k2SWpWbVptUTJOV1JqWlRFeVpqTmlPRFV6WVRZeU1HSmxPQ0lzSW1WdFlXbH
NJam9pYkd0c2JpSXNJbWxoZENJNk1UWXhNRFEwTWpJd05IMC5JdHA2WHh4aFRPMWJUc0oydGNM
ZU9hdFB3cWZWdWRsVmRQWkNnejB3eS1rIn0=; path=/; domain=http://localhost:3000.
I found a solution to this by adding this line to my server configuration:
app.use(cors({ origin: "http://localhost:3000", credentials: true }));
as well as setting withCredentials to true on my axios request.

Setting cookies on Graphql-yoga server throws a CORS error but it isn't

Edit: It definitely isn't actually CORS. It is like it is just ignoring my attempts to write the tokens into cookies... I am also having trouble getting it to throw an error that I can find useful... I will keep throwing darts at the wall until one makes sense.
I have a graphql-yoga server with an apollo client frontend. I am using cookie-parser on the server to store Microsoft Graph authentication tokens in the browser. I am getting an error that shows up as CORS but I can't figure out why.
Access to fetch at 'https://api.mydomain.store/' from origin 'https://mydomain.store' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Error: Network error: Failed to fetch
This is strange because I can make other queries to the server with no CORS issues. I thought it had to do with the way I was setting the options on the cookies, but I think that I am doing that correctly:
//saveCookies
function saveValuesToCookie(token: any, res: Response) {
// Parse the identity token
const user: any = jwt.decode(token.token.id_token);
const isDev = process.env.NODE_ENV === 'development';
console.log('isDev: ', isDev);
const cookieOpts: CookieOptions = {
domain: isDev ? 'localhost' : 'mydomain.store',
maxAge: 1000 * 60 * 60 * 24 * 365,
httpOnly: true,
sameSite: isDev ? 'lax' : true,
secure: isDev ? false : true,
};
// Save the access token in a cookie
res.cookie('graph_access_token', token.token.access_token, cookieOpts);
//save the users email to a cookie
res.cookie('graph_user_email', user.preferred_username, cookieOpts);
// Save the refresh token in a cookie
res.cookie('graph_refresh_token', token.token.refresh_token, cookieOpts);
res.cookie('graph_user_id', user.oid, cookieOpts);
// Save the token expiration tiem in a cookie
res.cookie(
'graph_token_expires',
token.token.expires_at.getTime(),
cookieOpts
);
}
Based on the resources I have seen so far, this seems correct with current browser rules.
So I look at where I am calling the mutation:
//apollo react authorize mutation
const AUTHORIZE_MUTATION = gql`
mutation($code: String!) {
authorize(code: $code) {
id
email
displayName
studentFaculty
}
}
`;
function AuthorizePage() {
const router = useRouter();
const { code } = router.query; //grab the code from the query params
const [authorize, { loading }] = useMutation(AUTHORIZE_MUTATION, {
variables: { code },
// refetchQueries: [{ query: ME_QUERY }],
onError: (error) => {
console.error(error); //**this is where the CORS error originates**
return Router.push('/');
},
update: (_, { data }) => {
console.log(data); //** never gets this far **
},
});
useEffect(() => {
if (router.query.code) {
authorize();
}
}, [router.query]);
if (loading) return <Loading />;
return <Loading />;
}
I am at a loss. Can anyone spot what is wrong or point me towards other places to look? The hardest part for me is that the code works perfectly on localhost serving client on port 3000 and server on port 4000.
I don't think this error is a genuine CORS issue because of the fact that I can make unauthenticated queries no problem.
Thanks for your time!