I am sending requests from the client to my Express.js server using Axios.
I set a cookie on the client and I want to read that cookie from all Axios requests without adding them manually to request by hand.
This is my clientside request example:
axios.get(`some api url`).then(response => ...
I tried to access headers or cookies by using these properties in my Express.js server:
req.headers
req.cookies
Neither of them contained any cookies. I am using cookie parser middleware:
app.use(cookieParser())
How do I make Axios send cookies in requests automatically?
Edit:
I set cookie on the client like this:
import cookieClient from 'react-cookie'
...
let cookie = cookieClient.load('cookie-name')
if(cookie === undefined){
axios.get('path/to/my/cookie/api').then(response => {
if(response.status == 200){
cookieClient.save('cookie-name', response.data, {path:'/'})
}
})
}
...
While it's also using Axios, it is not relevant to the question. I simply want to embed cookies into all my requests once a cookie is set.
You can use withCredentials property.
XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request.
axios.get(BASE_URL + '/todos', { withCredentials: true });
Also its possible to force credentials to every Axios requests
axios.defaults.withCredentials = true
Or using credentials for some of the Axios requests as the following code
const instance = axios.create({
withCredentials: true,
baseURL: BASE_URL
})
instance.get('/todos')
TL;DR:
{ withCredentials: true } or axios.defaults.withCredentials = true
From the axios documentation
withCredentials: false, // default
withCredentials indicates whether or not cross-site Access-Control requests should be made using credentials
If you pass { withCredentials: true } with your request it should work.
A better way would be setting withCredentials as true in axios.defaults
axios.defaults.withCredentials = true
It's also important to set the necessary headers in the express response. These are those which worked for me:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', yourExactHostname);
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
I am not familiar with Axios, but as far as I know in javascript and ajax there is an option
withCredentials: true
This will automatically send the cookie to the client-side. As an example, this scenario is also generated with passportjs, which sets a cookie on the server
So I had this exact same issue and lost about 6 hours of my life searching, I had the
withCredentials: true
But the browser still didn't save the cookie until for some weird reason I had the idea to shuffle the configuration setting:
Axios.post(GlobalVariables.API_URL + 'api/login', {
email,
password,
honeyPot
}, {
withCredentials: true,
headers: {'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'
}});
Seems like you should always send the 'withCredentials' Key first.
You can use withCredentials property to pass cookies in the request.
axios.get(`api_url`, { withCredentials: true })
By setting { withCredentials: true } you may encounter cross origin issue. To solve that
you need to use
expressApp.use(cors({ credentials: true, origin: "http://localhost:8080" }));
Here you can read about withCredentials
What worked for me:
Client Side:
import axios from 'axios';
const url = 'http://127.0.0.1:5000/api/v1';
export default {
login(credentials) {
return axios
.post(`${url}/users/login/`, credentials, {
withCredentials: true,
credentials: 'include',
})
.then((response) => response.data);
},
};
Note: Credentials will be the body of the post request, in this case the user login information (Normally obtained from the login form):
{
"email": "user#email.com",
"password": "userpassword"
}
Server Side:
const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 5000;
app.use(
cors({
origin: [`http://localhost:${port}`, `https://localhost:${port}`],
credentials: 'true',
})
);
Fatih's answer is still valid and great in 2022.
Also axios.defaults.withCredentials = true will do the trick.
It seems passing { withCredentials: true } to individual axios calls is deprecated.
How do I make Axios send cookies in requests automatically?
set axios.defaults.withCredentials = true;
or for some specific request you can use axios.get(url,{withCredentials:true})
this will give CORS error if your 'Access-Control-Allow-Origin' is set to
wildcard(*).
Therefore make sure to specify the url of origin of your request
for ex: if your front-end which makes the request runs on localhost:3000 , then set the response header as
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
also set
res.setHeader('Access-Control-Allow-Credentials',true);
for people still not able to solve it, this answer helped me.
stackoverflow answer: 34558264
TLDR;
one needs to set {withCredentials: true} in both GET request as well the POST request (getting the cookie) for both axios as well as fetch.
Another solution is to use this library:
https://github.com/3846masa/axios-cookiejar-support
which integrates "Tough Cookie" support in to Axios. Note that this approach still requires the withCredentials flag.
After trying for 2 days long and after trying out from the suggestions here this is what worked for me.
express:
cors: cors({ origin: "http:127.0.0.1:3000", credentials: true, })
Cookie : Make sure your cookie has secure: true, sameSite: "None"
Frontend(React)
axios.defaults.withCredentials = true;
(withCredentials : true did not work for me) to the places where you request the cookie as well as to the place where you send the cookie (GET/POST)
Hope this helps others as well.
You are getting the two thinks mixed.
You have "react-cookie" and "axios"
react-cookie => is for handling the cookie on the client side
axios => is for sending ajax requests to the server
With that info, if you want the cookies from the client side to be communicated in the backend side as well, you will need to connect them together.
Note from "react-cookie" Readme:
Isomorphic cookies!
To be able to access user cookies while doing server-rendering, you
can use plugToRequest or setRawCookie.
link to readme
If this is what you need, great.
If not, please comment so I could elaborate more.
For anyone where none of these solutions are working, make sure that your request origin equals your request target, see this github issue.
I short, if you visit your website on 127.0.0.1:8000, then make sure that the requests you send are targeting your server on 127.0.0.1:8001 and not localhost:8001, although it might be the same target theoretically.
This worked for me:
First, I had to make a new instance of axios with a custom config
Then, I used that axios instance to make a post request
See code below:
const ax = axios.create({
baseURL: 'yourbaseUrl',
withCredentials: true,
});
const loginUser = () => { const body ={username:state.values.email, password:state.values.password};
ax.post('/login',body).then(function(response){
return response}).then().catch(error => console.log(error));}
source:
https://www.npmjs.com/package/axios#creating-an-instance
This won't apply to everyone, but I was using a React frontend with Vite and it was serving the localhost as 127.0.0.1:5173, which is what I put as the CORS allowable domain. As soon as I both to localhost everything worked as expected!
// use this while creating axios instance
const API = axios.create({
baseURL: "http://localhost:4000", // API URL
withCredentials: true,
});
// USE THIS MIDDLEWARE in app.js of backend
first, install cors npm i cors
var cors = require("cors"); // This should be at the end of all middlewares
const corsOptions = {
origin: "http://localhost:3000",
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus: 200,
};
app.use(cors(corsOptions));
In my case, the problem was with the cookie, not with Axios; although I was receiving and sending the cookie from / to the same domain / subdomain / host, I was expecting it to work with different resources in different paths - but my coookie was acting like I had set it to a single Path, even though I omitted that attribute. Explicitly setting Path=/; in the cookie solved the issue.
Set the proxy in package.json(Frontend) and restart the server again (problem solved)
Related
I'm developing f/e page and b/e server.
f/e : react, localhost:3000
b/e : expresses, localhost:80
and I also hosted both server with different domain.
I have send a request using axis to the b/e server to get authorization cookie.
I received set-cookie header but i can't see it on my application tab.
I have searched a lot of posts.
They said that I should add an withCredentials option to axios config.
And also I need to add credentials option to b/e CORS setting.
So I fixed all of them.
But still I cannot get access to the cookies.
By the way, I cannot see or get access to the cookie. But if I send a request to B/E server, the cookies are sent correctly.
I have this issue for months.
Please help me out experts.
And please let me know if need more information.
here is my codes
// expressjs
app.use(cors({
origin: true,
credentials: true,
}));
...
res.cookie('SID', token, {
httpOnly: true, secure: true, sameSite: 'None'
});
res.cookie('SSID', token, {
httpOnly: false, secure: true, sameSite: 'None', path: '/'
});
...
I did it with/without defaults option and also with/without inline option
//react axios
...
axios.defaults.withCredentials = true;
...
const {status, data} = await axios.post(`${url}/api/blah`, {
something
}, {withCredentials: 'include'});
Response cookie
Chrome application cookie
I believe the right syntax for withCredentials on axios is:
//react axios
//...
axios.defaults.withCredentials = true;
//...
const {status, data} = await axios.post(`${url}/api/blah`, {
something
}, {withCredentials: true}); // instead of 'include'
I remember having an issue where axios.defaults.withCredentiasl = true solved it, but you probably don't want to keep it like that forever, so I'd suggest that you set it back to false after the call is finished. Also, the issue looks fixed on axios repo, so I'd try to solve it without changing the defauls.
I have 2 servers. One hosting a next.js application on localhost:5555 and another hosting an express server for the api on localhost:4444.
The authentication api returns a cookie however this is not being set in the browser running on localhost:5555.
res.cookie('cokkieName', 'bob', {
domain: '127.0.0.1:5555',
maxAge: 900000,
httpOnly: true,
});
res.status(200).json({
session: jwtSigned,
role: 'default',
});
My cors setup is:
const options: cors.CorsOptions = {
allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'X-Access-Token', 'Authorization'],
credentials: true,
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:5555',
preflightContinue: false,
};
I'd prefer to set the cookie with the api rather than on the via next.js.
I've tried alternating the cors settings but have had no success. The client call uses axios and has withCredentials set.
As mentioned in the other answer, you cannot set cookie on a different domain, as it can be a security risk.
But from your question...
Since the API server is running the authentication logic, the cookie should be set on the API server domain, NOT on client domain
So in your API server, you can change code to
res.cookie('cokkieName', 'bob', {
domain: '127.0.0.1:4444',
maxAge: 900000,
httpOnly: true,
});
or just remove the domain as it defaults to server domain.
res.cookie('cokkieName', 'bob', {
maxAge: 900000,
httpOnly: true,
});
As you have a client running on localhost:5555 and API server running on localhost:4444, it's a cross domain call and you need to pass withCredentials option to axios to pass the cookie. You might not be able to see the cookie in browser, as it can be a security risk, but can echo the cookie value on server console.
axios.get("url",{
withCredentials: true
}).then(res => {
console.log(res);
});
Note that credentials option work only if Access-Control-Allow-Origin header is not a wildcard like *
I have a created a server to mimic the code you posted. The server basically echoes the request cookie back.
It can be accessed through this stackblitz. You can toggle the withCredentials flag to see the difference in response
You can not set a cookie for other domains, Because that would be an enormous security risk.
But if you are looking for a way to share a cookie between your applications, the only way you have is to share a single domain between these applications, either separate them by url prefix or create one of them as a subdomain of other.
domain.com and api.domain.com. This way by specifying domain.com in your cookie both domains have access to it
If you concern about developing environment of yours, you can use Nginx and proxy_pass these two applications in single domain
What you are trying to do is definitely possible, the problem is that cookies are not port specific: Are HTTP cookies port specific? so you should specify a domain name but no port.
If you set a Cookie on its own domain (or if you don't specify it at all), all of the subsequent requests will carry the cookie even if initiated by a different port (only when withCredentials set to true, though). Also, note that, if set, the domain of the Cookie must match exactly the current domain, or parent domain (localhost is considered as different than 127.0.0.1)
Note: doing so shows the following warning in Chrome:
A cookie associated with a cross-site resource at http://example.com/
was set without the SameSite attribute. A future release of Chrome
will only deliver cookies with cross-site requests if they are set
with SameSite=None and Secure. You can review cookies in developer
tools under Application>Storage>Cookies and see more details at
https://www.chromestatus.com/feature/5088147346030592 and
https://www.chromestatus.com/feature/5633521622188032.
So, prepare your code for the future web standards!
I had a similar problem with nuxt.js. To set cookies in your browser using express, I suggest using the library Cookies
In my case I set cookies in express like this:
setResponseCookie(req, res, token, strategyName) {
const isHttps = req.protocol === 'https';
let { cookie: { maxAge } } = config;
const cookieSettings = isHttps => ({
maxAge,
httpOnly: true,
secure: isHttps,
});
const cookies = new Cookies(req, res);
cookies.set('auth.strategy', strategyName, cookieSettings(isHttps))
cookies.set('auth.token', token, cookieSettings(isHttps))
}
and then in express route you can add to this like:
let token = sign(user);
this.setResponseCookie(req, res, token, 'local-login')
res.json({ token, status: 'success' });
or
let token = sign(req.user);
this.setResponseCookie(req, res, token, 'google')
res.redirect(config.client.url);
I managed to fix this issue of the server not setting the browser cookies by setting my res.cookie like the following
res.cookie("Test", "If you see this then it works", {httpOnly: false, secure: true, sameSite: "none"}).json({message: "Finished"});
I checked to see if the cookie was set by opening up the chrome dev tools, going to the application, then cookies, then the site URL.
There are a few things I would like to preference about the above code. This method only works on a URL starting with https, this means it will not fix the problem if on a URL like http://localhost:3000. I don't know if it matters if httpOnly is true or false. Also, What is necessary when setting the cookie this way is to pass in the properties secure: true, sameSite: "none". Passing these values into res.cookie was what finial fixed the issue for me. I have not seen this solution posted anywhere and decided I out to let you know.
My code that fetches the data from the API and triggers the server to set the browser cookie looks like this for anyone who is interested.
fetch(domain.com/testSetCookie', {
method: 'get',
mode: "cors",
credentials: 'include' //<-- important
})
.then((res) => {
//console.log(res);
return; //res.json();
})
.then((data) => {
console.log(data);));
})
.catch((err) => {
console.log("an error happeoned");
console.log(err);
});
I have a Koa and GraphQL API with an authentication resolver which adds sets an auth token to the response cookies.
My API runs on http://localhost:3000 and my front end runs on http://localhost:3001. In order to make this work, I needed to add withCredentials flag to my API request as well as configure my Koa/Cors options with { origin: false, credentials: true }. Without these settings I would get a CORS error which I find confusing because they are both on localhost so technically the same domain...?
There will be multiple front ends using this API and will need to authenticate and get the auth token set in the cookie. When I spoof my domain name as http://app1.dev & http://app2.dev, both calling the API on http://localhost:3000, the auth token gets set in the response cookies but the browser does not store the cookie like it did when everything was localhost.
On my API, I am using #koa/cors for my configuring my cors and then set the following:
app.use(cors({ origin: false, credentials: true }))
On my front end react app, I have a request function which I use for all my GraphQL queries:
const request = query => {
const request = axios.create({
baseURL: apiUrl,
headers: {'X-apikey': apiKey},
withCredentials: true,
});
return request.post('/graphql', { query })
.then(res => res.data)
.catch(console.error)
}
Why are the cookies not getting set in the browser?
EDIT: Updated my axios request to create instance of axios and attempting to set withCredentials. I am still not seeing the credentials param in my request in dev tools though. I suspect this could be the issue?
I am using Express.js server. With cookie-parser I have opened this endpoint
app.get("/s", (req,res) => {
res.cookie("bsaSession", req.session.id)
res.send("set cookie ok")
})
When I manually use the browser to http://localhost:5555/s where I have the website running the browser debug console shows that the cookie have been applied.
But when I use fetch API to do the equivalent, it does not set the cookie.
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'same-origin'
})
}
Why?
I have found the solution. The core of this problem being that my button to trigger the fetch is on http://localhost:3000/. The server is on http://localhost:5555/ (I am simulating real environment on my own machine)
The problem is that this fetch call
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'same-origin'
})
}
Without credentials, the browser cannot send or receive cookies via fetch (https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
With credentials as same-origin I can see the cookies coming from the server in the Set-Cookie response header, but nothing is being stored in the browser. One strange thing is that this response always have HttpOnly tagged after the cookie string regardless of my {httpOnly : true/false} settings on the server. In the case of manually using the browser to the page to do GET request, HttpOnly is being respected as usual, and the cookies are set.
So the solution is to set credentials as include to allow cross-origin cookie sending.
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'include'
})
}
Also, on the server side you need to allow a particular origin manually with new headers:
app.get("/s", (req,res) => {
res.cookie("bsaSession", req.session.id, {httpOnly:false})
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
res.header('Access-Control-Allow-Credentials','true'
res.send("set")
})
Not doing this results in
XMLHttpRequest cannot load http://localhost:5555/s. Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
But the cookie will be set regardless of this error. Still nice to include that header to silence the error.
If you are using cors middleware for Express it is even easier. You can just use these options
var corsOptions = {
origin: 'http://localhost:3000',
credentials: true
}
app.use(cors(corsOptions))
And of course credentials: 'include' is still required at the client side.
5argon's solution was great for me otherwise, but I had to set origin in express cors to true. So in backend:
app.use(
cors({
origin: true,
credentials: true,
})
);
And in fetch:
fetch("http://localhost:5555/s", {
method: 'GET',
credentials: 'include'
})
I have a setup involving
Frontend server (Node.js, domain: localhost:3000) <---> Backend (Django, Ajax, domain: localhost:8000)
Browser <-- webapp <-- Node.js (Serve the app)
Browser (webapp) --> Ajax --> Django(Serve ajax POST requests)
Now, my problem here is with CORS setup which the webapp uses to make Ajax calls to the backend server. In chrome, I keep getting
Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
doesn't work on firefox either.
My Node.js setup is:
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', 'http://localhost:8000/');
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
};
And in Django I'm using this middleware along with this
The webapp makes requests as such:
$.ajax({
type: "POST",
url: 'http://localhost:8000/blah',
data: {},
xhrFields: {
withCredentials: true
},
crossDomain: true,
dataType: 'json',
success: successHandler
});
So, the request headers that the webapp sends looks like:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: "Origin, X-Requested-With, Content-Type, Accept"
Access-Control-Allow-Methods: 'GET,PUT,POST,DELETE'
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: csrftoken=***; sessionid="***"
And here's the response header:
Access-Control-Allow-Headers: Content-Type,*
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE
Content-Type: application/json
Where am I going wrong?!
Edit 1: I've been using chrome --disable-web-security, but now want things to actually work.
Edit 2: Answer:
So, solution for me django-cors-headers config:
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000' # Here was the problem indeed and it has to be http://localhost:3000, not http://localhost:3000/
)
This is a part of security, you cannot do that. If you want to allow credentials then your Access-Control-Allow-Origin must not use *. You will have to specify the exact protocol + domain + port. For reference see these questions :
Access-Control-Allow-Origin wildcard subdomains, ports and protocols
Cross Origin Resource Sharing with Credentials
Besides * is too permissive and would defeat use of credentials. So set http://localhost:3000 or http://localhost:8000 as the allow origin header.
If you are using CORS middleware and you want to send withCredential boolean true, you can configure CORS like this:
var cors = require('cors');
app.use(cors({credentials: true, origin: 'http://localhost:3000'}));
Expanding on #Renaud idea, cors now provides a very easy way of doing this:
From cors official documentation found here:
"
origin: Configures the Access-Control-Allow-Origin CORS header.
Possible values:
Boolean - set origin to true to reflect the request origin, as defined by req.header('Origin'), or set it to false to disable CORS.
"
Hence we simply do the following:
const app = express();
const corsConfig = {
credentials: true,
origin: true,
};
app.use(cors(corsConfig));
Lastly I think it is worth mentioning that there are use cases where we would want to allow cross origin requests from anyone; for example, when building a public REST API.
try it:
const cors = require('cors')
const corsOptions = {
origin: 'http://localhost:4200',
credentials: true,
}
app.use(cors(corsOptions));
If you are using express you can use the cors package to allow CORS like so instead of writing your middleware;
var express = require('express')
, cors = require('cors')
, app = express();
app.use(cors());
app.get(function(req,res){
res.send('hello');
});
If you want to allow all origins and keep credentials true, this worked for me:
app.use(cors({
origin: function(origin, callback){
return callback(null, true);
},
optionsSuccessStatus: 200,
credentials: true
}));
This works for me in development but I can't advise that in production, it's just a different way of getting the job done that hasn't been mentioned yet but probably not the best. Anyway here goes:
You can get the origin from the request, then use that in the response header. Here's how it looks in express:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.header('origin') );
next();
});
I don't know what that would look like with your python setup but that should be easy to translate.
(Edit) The previously recomended add-on is not available any longer, you may try this other one
For development purposes in Chrome, installing
this add on will get rid of that specific error:
Access to XMLHttpRequest at 'http://192.168.1.42:8080/sockjs-node/info?t=1546163388687'
from origin 'http://localhost:8080' 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'. The credentials mode of requests
initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
After installing, make sure you add your url pattern to the Intercepted URLs by clicking on the AddOn's (CORS, green or red) icon and filling the appropriate textbox. An example URL pattern to add here that will work with http://localhost:8080 would be: *://*
Though we have many solutions regarding the cors origin, I think I may add some missing part. Generally using cors middlware in node.js serves maximum purpose like different http methods (get, post, put, delete).
But there are use cases like sending cookie response, we need to enable credentials as true inside the cors middleware Or we can't set cookie. Also there are use cases to give access to all the origin. in that case, we should use,
{credentials: true, origin: true}
For specific origin, we need to specify the origin name,
{credential: true, origin: "http://localhost:3000"}
For multiple origins,
{credential: true, origin: ["http://localhost:3000", "http://localhost:3001" ]}
In some cases we may need multiple origin to be allowed. One use case is allowing developers only. To have this dynamic whitelisting, we may use this kind of function
const whitelist = ['http://developer1.com', 'http://developer2.com']
const corsOptions = {
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error())
}
}
}
Had this problem with angular, using an auth interceptor to edit the header, before the request gets executed. We used an api-token for authentification, so i had credentials enabled. now, it seems it is not neccessary/allowed anymore
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
//withCredentials: true, //not needed anymore
setHeaders: {
'Content-Type' : 'application/json',
'API-TOKEN' : 'xxx'
},
});
return next.handle(req);
}
Besides that, there is no side effects right now.
CORS ERROR With NETLIFY and HEROKU
Actually, if none of the above solutions worked for you then you might wanna try this.
In my case, the backend was running on Heroku and the frontend was hosted on netlify.
in the .env file, of the frontend, the server_url was written as
REACT_APP_server_url = "https://ci-cd-backend.herokuapp.com"
and in the backend, all my api calls where written as,
app.get('/login', (req, res, err) => {});
So, Only change you need to do is, add /api at the end of the routes,
so, frontend base url will look like,
REACT_APP_server_url = "https://ci-cd-backend.herokuapp.com/api"
and backend apis should be written as,
app.get('/api/login', (req, res, err) => {})
This worked in my case, and I believe this problem is specifically related when the front end is hosted on netlify.