Heroku: cannot share httpOnly cookies between subdomains - node.js

I build the simple application that shows GitHub repositories. Here is the link for the app: https://wemake-services-test-client.herokuapp.com (firstly, you need to authenticate to GitHub). It seems to me that fetch doesn't send httpOnly cookies. Click "Sign In" and open the console:
Console Snapshot
Backend handler for route '/login' sets the httpOnly cookie:
import { Request, Response } from 'express';
import { createOAuthAppAuth } from '#octokit/auth-oauth-app';
import cookieOptions from '../cookieOptions';
export default async function login(req: Request, res: Response) {
const code = req.query.code as string;
const appAuth = createOAuthAppAuth({
clientType: 'oauth-app',
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
});
try {
const userAuth = await appAuth({
type: 'oauth-user',
code,
});
const { token } = userAuth;
res.cookie('ws_token', token, cookieOptions);
res.json({ isAuthenticated: true });
...
Then I make GET to '/user' route which has the authMiddleware that checks if there is 'ws_token' in req.cookies and send status 401 (Unauthorized) if not.
authMiddleware.ts:
export default function authMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
if (!req.cookies.ws_token) {
res.sendStatus(401);
return;
}
next();
}
So I got 401 error on '/user' because fetch doesn't send the httpOnly cookie ws_token from client side.
...
async fetchUser(): Promise<GithubUser> {
const response = await fetch(`${API_URL}/user`, {
headers: {
Accept: 'application/json',
},
credentials: 'include',
});
const user: GithubUser = await response.json();
return user;
}
...
It works on localhost (without flag secure and domain set to localhost), but not on Heroku. But why? How to fix it?
Source code:
frontend – https://github.com/standbyoneself/ws-test-client (calls to API using fetch are in src/services/GithubService.ts)
backend – https://github.com/standbyoneself/ws-test-server

Solved by disabling "Prevent cross-site tracking" in Safari Security Settings.

Related

Set a cookie in Nest JS using POST request (ViteJs + REACT JS)

I am using nest js and want to set the cookies when the user will hit a specific endpoint:
#Get()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setCookie('key', 'value')
}
This code works and the cookies are set in cookies storage from the Application tab in Chrome. If i try setting cookies using post:
#Post()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setCookie('key', 'value')
}
My code on UI:
try {
const response = await axios.post(
`http://localhost:3000/api/v1/hello`,
user,
{
method: 'post',
headers: {
withCredentials: true,
},
data: user,
},
);
if (response.data) {
// sss
}
} catch (err) {
if (err instanceof AxiosError) {
if (err.response) {
toast.error(err.response.data.message);
}
}
}
main.js file
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
app.useGlobalPipes(new ValidationPipe());
app.use(cookieParser());
app.enableCors({ origin: 'http://127.0.0.1:5173', credentials: true });
await app.listen(3000);
}
bootstrap();
...then the cookies storage is empty and no cookie is set. Question: Why get request works but post not and how to solve this?
I think the GET request works because it is an HTTP method that allows for retrieving data from a server, and the setCookie() method is used to set a cookie in the response.
The POST request doesn't work because POST is typically used for creating or modifying data on a server, and the setCookie() method is not designed to handle data modifications.
It works with POST method as well. Try something similar like this:
#Post()
setCookiesApi(#Res({ passthrough: true }) response:Response) {
response.setHeader('Set-Cookie', cookie);
return response.send();
}

Unable to retrieve a refresh cookie in expressjs (react and nodejs app)

I am unable to retrieve a cookie that I sent earlier.
As part of login, I sent back a refresh token as an httpOnly cookie.
const payload = {name, email};
console.log("payload: ", payload);
const accessToken = jsonwebtoken.sign(payload, process.env.ACCESS_TOKEN_KEY, { expiresIn: '15m' });
const refreshToken = jsonwebtoken.sign(payload, process.env.REFRESH_TOKEN_KEY, { expiresIn: '1d' });
console.log("Access Token:", accessToken); // access token is generated
console.log("Refresh Token:", refreshToken); // refresh token is generated
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: false, sameSite: 'Lax', maxAge: 24*60*60*1000 }); // call succeeded. what is the name of cookie?
res.json({ accessToken });
Later on a refresh endpoint I look for a cookie and can't find it:
export const handleRefreshToken = async (req, res) => {
console.log("Request Cookies", req.cookies);
const cookies = req.cookies;
if (!cookies?.refreshToken) return res.sendStatus(401);
I see the following cookies:
_ga: 'xxxxxxxxxxxxxxxxx',
_gid: 'xxxxxxxxxxxxxxxx',
_gat_gtag_UA_xxxxxx: 'x',
_ga_QPY49S2WC4: 'xxxxxxxxxxxxxxxxxxx'
This is on my dev environment with nodejs running on localhost:5000.
Update: Using devtools (Network) I see the cookie in the response on the client side. The name of the cookie is 'refreshToken'. However, the cookie doesn't show up on the browser when I look at the cookies on the browser. Perhaps, the cookie isn't being retained on the browser. Any suggestions on why that could be?
Update2: The link provided by #Konrad Linkowski worked. When the axios request is made from the client, I needed the option "{ withCredentials: true }".
The error was on the client end. The express code functioned correctly. This link explains it: Does Axios support Set-Cookie? Is it possible to authenticate through Axios HTTP request?
My original call on the client side (using axios) was:
const res = await axios.post('/login', { ident: email, password });
Instead it should have been:
const res = await axios.post('/login', { ident: email, password }, { withCredentials: true });

cookies are not accessible from getServerSideProps in NextJs

i have an issue when trying to send the cookies from browser to nextJs server
first let me describe the issue :
my backend API is built with Nodejs and express, the frontend is
handled by Nextjs , when the user try to login the token is stored as
an httpOnly cookie ,to get the loggedin user i must send the cookie
from my frontend to my backend so i do that through nextJs
getServerSideProps function with the help of
Context.req.headers.cookie in order to sent this token to my nodeJs
backend.
this is the login controller (backend):
exports.login = catchAsync(async (req,res,next)=>{
// SOME CODE
const token = jwt.sign({id: user.id}, process.env.JWT_SECRET);
const cookieOption = {
expires: new Date(Date.now() + process.env.JWT_COOKIE_EXPIRES_IN*24*60*60*1000),
httpOnly: true,
secure : true,
sameSite: 'none',
// domain: '.herokuapp.com'
};
res.cookie('jwt', token, cookieOption);
res.status(200).json({
status: 'success',
data: {
user
}
})
});
and this is my getServerSideProps function in which i try to send the token from nextJs to nodeJs server:
export async function getServerSideProps(context){
console.log(context.req.headers.cookie) // EMPTY OBJECT!
console.log(context.req.cookies) //EMPTY OBJECT TOO
let user
try{
const res = await fetch(`${url}/users/isLoggedin`, {
method: 'GET',
credentials:'include',
headers: {
'Access-Control-Allow-Credentials': true,
Cookie: req.headers.cookie
},
})
const data = await res.json()
if(!res.ok){
throw data
}
user = data
}catch(err){
return console.log(err)
}
return {
props: {
user,
}
}
}
i can send the token from browser directly to nodeJs (client side) but i can't send the token from the browser to nextJs server i don't know why, i have tried many solutions but no ones workes for me .
note: both backend and frontend are deployed to heroku

No 'Access-Control-Allow-Origin' header is present on the requested resource when deployed to Heroku

So I have a simple Heroku app that just signs people up to my Mailchimp contacts list. I have it working locally, but when deploying the app I get CORS errors.
I've tried adding withCredentials: true to my axios request and adding cors origin in my Express app, but it still doesn't work.
I have been going through all kinds of articles on cors issues trying to get this to work and understand why this is happening.
I've replaced some of the sensitive information with dummy text, but this is essentially how the request is being made and what the server is doing with the information.
React Code:
const submitForm = async (values) => {
const { email } = values;
try {
const payload = {
email_address: email
};
await axios.post("https://some-url.herokuapp.com", payload, { withCredentials: true });
handleMessage("Thank you for signing up.");
} catch (error) {
console.log(error.message);
handleMessage(error.message);
}
}
Express Code:
const cors = require('cors');
app.use(cors({
origin: `https://www.mycustomdomain.com/`,
credentials: true
}));
app.post('/', jsonParser, (req, res, next) => {
const { email_address } = req.body
if (!email_address) {
return res.status(400).json({
error: `Missing email in request body`
})
}
const data = {
members: [
{
email_address,
status: 'subscribed'
}
]
}
const payload = JSON.stringify(data);
const options = {
url: `https://mailchim-api.com`,
method: 'POST',
headers: {
Authorization: `auth authCode`
},
body: payload
}
request(options, (err, response, body) => {
if (err) {
res.status(500).json(err)
} else {
if (response.statusCode === 200) {
res.status(200).json(body)
}
}
})
})
My initial problem was that I didn't manually enter the env variables used in the Express app into Heroku.
I had to run heroku config:set APIKEY=SOME-PASSWORD in order to get all of my env variables to be used on Heroku.
https://devcenter.heroku.com/articles/config-vars
Because I didn't do this originally, I made the mistake of replacing all of my env variables with the actual strings which brought about the CORS issue. Mailchimp also deactivated my API key since the key was being published online. So it was actually a layered issue, not exactly a CORS issue, to begin with.

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

Resources