I've created API in node express and I'm running it on port :8000, I am consuming APIs through simple CRA on port :3000. I've created registration and login with setting httpOnly cookie. Furthermore, I've put middleware to check each endpoint in order to verify if it has that token.
When I test through Thunder/Postman everything works, after logging in I get the cookie in response, I set that cookie as auth token and make request to get data and I get the data.
When I log in through the React Frontend it succeeds and I can see in network tab that I have received the cookie in response. But when I make a request to protected endpoint, the request does not have a cookie in it (I log incoming requests on server and compare ones made with Thunder/Postman client and via app in Browser).
I use axios, and I've put {withCredentials: true} it doesn't work. I've used withAxios hook and it doesn't work either.
SERVER
index.js
...
const app = express()
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
}));
...
controllers/User.js
...
const loginUser = async(req, res) => {
const body = req.body
const user = await User.findOne({ email: body.email })
if(user) {
const token = generateToken(user)
const userObject = {
userId: user._id,
userEmail: user.email,
userRole: user.role
}
const validPassword = await bcrypt.compare(body.password, user.password)
if(validPassword) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Access-Control-Allow-Credentials', 'true');
res.set(
'Access-Control-Expose-Headers',
'date, etag, access-control-allow-origin, access-control-allow-credentials'
)
res.cookie('auth-token', token, {
httpOnly: true,
sameSite: 'strict'
})
res.status(200).json(userObject)
} else {
res.status(400).json({ error: "Invalid password" })
}
} else {
res.status(401).json({ error: "User doesn't exist" })
}
}
...
middleware.js
...
exports.verify = (req, res, next) => {
const token = req.headers.authorization
if(!token) res.status(403).json({ error: "please provide a token" })
else {
jwt.verify(token.split(" ")[1], tokenSecret, (err, value) => {
if(err) res.status(500).json({error: "failed to authenticate token"})
req.user = value.data
next()
})
}
}
...
router.js
...
router.get('/bills', middleware.verify, getBills)
router.post('/login', loginUser)
...
CLIENT
src/components/LoginComponent.js
...
const loginUser = (e) => {
setLoading(true)
e.preventDefault()
let payload = {email: email, password: password}
axios.post('http://localhost:8000/login', payload).then(res => res.status === 200
? (setLoading(false), navigate('/listbills')) : navigate('/register'))
}
...
src/components/ListBills.js
...
useEffect(() => {
fetch('http://localhost:8000/bills', {
method: 'get',
headers: {'Content-Type': 'application/json'},
credentials: 'include',
})
.then(response => {console.log(response)}).catch(err => console.log(err));
}, [])
...
I've also tried:
axios.get('http://localhost:8000/bills',{withCredentials: true})
.then((data) => console.log(data))
.then((result) => console.log(result))
.catch((err) => console.log('[Control Error ] ', err))
}
and
const [{ data, loading, error }, refetch] = useAxios(
'http://localhost:8000/bills',{
withCredentials: true,
headers: {'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'
}})
Console.log error:
After I login I get this in Network tab:
However when I want to access the list:
=== UPDATE ===
So the cause of the issue is not having the httpOnly cookie passed in the request header. This is the log of the middleware I am using:
token undefined
req headers auth undefined
req headers {
host: 'localhost:8000',
connection: 'keep-alive',
'sec-ch-ua': '" Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36',
'sec-ch-ua-platform': '"macOS"',
'content-type': 'application/json',
accept: '*/*',
origin: 'http://localhost:3000',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,hr;q=0.8,sr;q=0.7,bs;q=0.6,de;q=0.5,fr;q=0.4,it;q=0.3'
}
token is read from headers.authorization but from the log of the headers it doesn't exist so my requests fail to be authorized.
Still not working.
After reading everything on CORS and httpOnly cookies I've managed to get it working.
First I removed sameSite and added domain prop according to documentation in controllers/User.js on SERVER
res.cookie('auth-token', token, {
httpOnly: true,
domain: 'http://localhost:3000'
})
Then I got a little yellow triangle in the console request view, it said that domain was invalid. Then I just changed domain to origin and the cookie appeared in the request log of the headers 🎉
res.cookie('auth-token', token, {
httpOnly: true,
origin: 'http://localhost:3000',
})
The cookie was not in the Authorization property of the headers but in the cookie so I had to change the code in the middleware.js since it expected format bearer xxyyzz but receiving auth-token=xxyyzz, it looks like this now:
exports.verify = (req, res, next) => {
const token = req.headers.cookie
if(!token) res.status(403).json({ error: "please provide a token" })
else {
jwt.verify(token.split("=")[1], tokenSecret, (err, value) => {
if(err) res.status(500).json({error: "failed to authenticate token"})
req.user = value.data
next()
})
}
}
Related
I am trying to send a DELETE request to my backend server, but I keep getting this response printed to my console:
Response {type: 'cors', url: 'http://localhost:3003/delete', redirected: false, status: 200, ok: true, …}
body: (...)
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "cors"
url: "http://localhost:3003/delete"
[[Prototype]]: Response
I don't know why this is happening.
server.js
const express = require('express')
const knex = require('knex')
const cors = require('cors')
const db = knex({
client: 'pg',
connection: {
host: '127.0.0.1',
user: 'postgres',
password: 'psql',
database: 'blogspot',
port: 5432
}
});
const app = express();
app.use(express.json())
app.use(cors())
// Delete Blog
app.delete('/delete', (req, res) => {
const {id} = req.body;
db.select('*').from('blogs')
.where({
id: id
})
.del()
.then(() => {
res.json('Deleted Successfully')
})
.catch(err => res.status(404).json('An error occured'))
})
fetchAPI.js
function deleteBlog (blog) {
fetch('http://localhost:3003/delete', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(blog)
}).then(resp => {
console.log(resp)
if (resp === 'Deleted Successfully') {
navigate(0)
} else if (resp === 'An error occured') {
console.log('Something went wrong')
} else {
console.log('ERROR')
}
})
}
I keep getting 'ERROR' printed to my console along with the cors response I pasted above. When I refresh, I find that the blog has been deleted, but the response was definitely an error since navigate(0) wasn't run and ERROR was printed to my console. I have tried removing the 'Content-Type': 'application/json' header and sending the id as request params instead but I got the same error.
The fact that the response is of type "cors" just means that some contents are filtered by CORS policy (see https://developer.mozilla.org/en-US/docs/Web/API/Response/type) but you didn't get any error code, the statusCode is 200.
Since your response content type is JSON, you must also resolve the json parsing before reading the response:
function deleteBlog(blog) {
fetch('http://localhost:3003/delete', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(blog)
})
.then(data => data.json())
.then(resp => {
// I also suppose that you will more likely find
// your "Deleted successfully" in the resp.body property, so :
if (resp.body === 'Deleted Successfully') {
navigate(0)
} else if (resp.body === 'An error occured') {
console.log('Something went wrong')
} else {
console.log('ERROR')
}
})
}
I have build a React Redux app. It works fine in localhost in every browser(chrome,edge,firefox). But the Heroku deployed app doesn't works in Edge, Firefox , although it worked in chrome perfectly.
My app doesn't seems to send request and receive response ,because i noticed that the login button spinner keeps on spinning because it waits for response. So i think that no response is received from backend.
Following are the conclusion i made after test in different browser:
it works perfectly in every browser in localhost development mode.
After Deploying , herokuapp works perfectly fine in chrome only.
Herokuapp doesn't work in edge , Firefox.
Same is the issue with other system(Friends PC).
It doesn't work in any browser except chrome in Mobile device
After console logging and banging my head for hours i got the following error in microsoft edge:
Access to XMLHttpRequest at 'https://ecrypt.herokuapp.com/user/login' from origin 'http://ecrypt.herokuapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://ecrypt.herokuapp.com' that is not equal to the supplied origin.
2.2c59d01c.chunk.js:2 POST https://ecrypt.herokuapp.com/user/login net::ERR_FAILED
Okay, so i figured out that there is some problem with CORS.
Following is my code
Frontend:
import axios from "axios";
const API = axios.create({
baseURL: "https://ecrypt.herokuapp.com",
// withCredentials: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token",
},
});
// const API = axios.create({ baseURL: "http://localhost:9000" });
const cloudinaryAPI = axios.create({
baseURL: "https://api.cloudinary.com/v1_1/ecryptimgdb",
});
//register new user
export const registerNewUser = (formData) =>
API.post("/user/register", formData);
//Account Activation through Email
export const activation = (activation_token) =>
API.post("/user/activation", {
data: {
activation_token,
},
});
//Login
export const login = (formData) =>
API.post("/user/login", formData, { withCredentials: true });
//get Token
export const getToken = () =>
API.post("/user/refresh_token", null, { withCredentials: true });
//Logout
export const logout = () => API.get("/user/logout", { withCredentials: true });
//get User
export const getUser = (token) =>
API.get("/user/info", {
headers: { Authorization: `${token}` },
});
//PROFILE SETTINGS__________________________________________________________________________________________
export const editProfile = (token, profileData) =>
API.post(
"/user/updateProfile",
{ profileData },
{
headers: { Authorization: `${token}` },
}
);
//forgot password____
export const forgotPass = (email) =>
API.post("/user/forgotPassword", { email });
//reset password_____
export const resetPass = (token, password) =>
API.post(
"/user/resetPassword",
{ password },
{
headers: { Authorization: `${token}` },
}
);
//change password____
export const changePass = (oldPassword, newPassword, token) =>
API.post(
"/user/changePassword",
{ oldPassword, newPassword },
{
headers: { Authorization: `${token}` },
}
);
BACKEND:
//IMPORTS
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const cors = require("cors");
const cookiesParser = require("cookie-parser");
const path = require("path");
const app = express();
// app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
app.use(cors({ credentials: true, origin: "https://ecrypt.herokuapp.com" }));
app.use(cookiesParser());
// app.use(bodyParser.json({ limit: "30mb", extended: true }));
// app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const CONNECTION_URL = process.env.MONGODB_URL;
// const CONNECTION_URL = process.env.MONGODB_LOCAL_URL;
const PORT = process.env.PORT || 9000;
//MONGODB CLOUD DATABASE CONNECTION________________________
mongoose
.connect(CONNECTION_URL, {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.then(() => console.log("Connected to Database :: MongoDB Cloud"))
.catch((err) => console.log(err.message));
// app.use("/", routesIndex);
app.use("/", require("./routes/index"));
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}
//SERVER LISTENING
app.listen(PORT, (err) => {
if (err) {
console.log(err.message);
} else {
console.log(`Listening on localhost:${PORT}`);
}
});
NOTE
I am using cookies to store token so i needed withCredentials and Authorization headers.
The following headers should be sent by the server (instead of the front)
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token",
},
Try removing headers from your axios request. I don't think those headers are allowed.
Ok, so i figured it out ,in my case i was setting headers as
{
headers: { Authorization: `${token}` },
}
Instead of setting it like above a slight change just worked for me:
{
headers: { Authorization: `Bearer ${token}` },
}
and at backend side in index.js or server.js whatever your file name is ,use cors middle ware like this:
const cors = require("cors");
app.use(
cors({
origin: ["https://blahblah.herokuapp.com", "http://localhost:****"],
credentials: true,
})
);
Note: credentials true if you want to pass cookies and access them at server side.
In my case i wanted to access the HttpOnly cookies at server side.
This question already has answers here:
'Access-Control-Allow-Credentials' header in the response is '' which must be 'true'
(2 answers)
Closed 2 years ago.
I am learning server-client communication in the course of making MMORPG project.
*update: server side code is edited.
This is server side code.
router.post('/login', async (request, response, next) => {
passport.authenticate('login', async (error, user) => {
try {
if (error) {
return next(error);
}
if (!user) {
return next(new Error('email and password are required'));
}
request.logIn(user, { session: false }, (err) => {
if (err) {.....
This is client side code
function postData(url, data = {}) {
return fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
redirect: 'follow',
body: JSON.stringify(data),
}).then((response) => response.json());
}
login() {
const loginValue = this.loginInpout.value;
const passwordValue = this.passwordInput.value;
postData('http://localhost:4000/login', { email: loginValue, password: passwordValue })
.then((response) => {
if (response.status === 200) {
this.startScene('Game');
} else {
console.log(response.message);
window.alert('invald username or password');
}
}).catch((error) => {
console.log(error.message);
window.alert('invald username or password');
});
}
when login() function is called, fetch() function throws this message in browser console.
(http://localhost:4000/login) is server side and (http://localhost:8000) is client side.
Access to fetch at 'http://localhost:4000/login' from origin 'http://localhost:8000'
has been blocked by CORS policy: Response to preflight request doesn't pass access
control check: The value of the 'Access-Control-Allow-Credentials' header in the
response is '' which must be 'true' when the request's credentials mode is 'include'.
LoginScene.js:48 POST http://localhost:4000/login net::ERR_FAILED
Failed to fetch <<-- fetch error message on browser console
I tried to fix it many different ways with no good outcome.
Try the following code:
import express from "express";
import http from "http";
const app = express();
const server = http.createServer(app);
const sio = require("socket.io")(server, {
handlePreflightRequest: (req, res) => {
const headers = {
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Allow-Origin": req.headers.origin,
"Access-Control-Allow-Credentials": true
};
res.writeHead(200, headers);
res.end();
}
});
sio.on("connection", () => {
console.log("Connected!");
});
Even though this question is asked several times at SO like:
fetch: Getting cookies from fetch response
or
Unable to set cookie in browser using request and express modules in NodeJS
None of this solutions could help me getting the cookie from a fetch() response
My setup looks like this:
Client
export async function registerNewUser(payload) {
return fetch('https://localhost:8080/register',
{
method: 'POST',
body: JSON.stringify(payload),
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
}
...
function handleSubmit(e) {
e.preventDefault();
registerNewUser({...values, avatarColor: generateAvatarColor()}).then(response => {
console.log(response.headers.get('Set-Cookie')); // null
console.log(response.headers.get('cookie')); //null
console.log(document.cookie); // empty string
console.log(response.headers); // empty headers obj
console.log(response); // response obj
}).then(() => setValues(initialState))
}
server
private setUpMiddleware() {
this.app.use(cookieParser());
this.app.use(bodyParser.urlencoded({extended: true}));
this.app.use(bodyParser.json());
this.app.use(cors({
credentials: true,
origin: 'http://localhost:4200',
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
credentials: true
}));
this.app.use(express.static(joinDir('../web/build')));
}
...
this.app.post('/register', (request, response) => {
const { firstName, lastName, avatarColor, email, password }: User = request.body;
this.mongoDBClient.addUser({ firstName, lastName, avatarColor, email, password } as User)
.then(() => {
const token = CredentialHelper.JWTSign({email}, `${email}-${new Date()}`);
response.cookie('token', token, {httpOnly: true}).sendStatus(200); // tried also without httpOnly
})
.catch(() => response.status(400).send("User already registered."))
})
JavaScript fetch method won't send client side cookies and silently ignores the cookies sent from Server side Reference link in MDN, so you may use XMLHttpRequest method to send the request from your client side.
I figured it out. The solution was to set credentials to 'include' like so:
export async function registerNewUser(payload) {
return fetch('https://localhost:8080/register',
{
method: 'POST',
body: JSON.stringify(payload),
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
}
After that I needed to enabled credentials in my cors middleware:
this.app.use(cors({
credentials: true, // important part here
origin: 'http://localhost:4200',
optionsSuccessStatus: 200
})
And then finally I needed to remove the option {httpOnly: true} in the express route response:
response.cookie('token', '12345ssdfsd').sendStatus(200);
Keep in mind if you send the cookie like this, it is set directly to the clients cookies. You can now see that the cookie is set with: console.log(document.cookie).
But in a practical environment you don't want to send a cookie that is accessible by the client. You should usually use the {httpOnly: true} option.
I've set up an API with a create user and an auth route. The auth route should set an httpOnly cookie containing a JWT, and should send JSON for the client to store in localhost.
In the front-end I'm doing a simple fetch.
The server responds 200 and with the JSON I expect, but somehow, the cookie doesn't get set.
However, in Postman, the cookie does indeed get set.
Express server
const express = require('express')
const cors = require('cors')
// boilerplate stuff
app.use(express.json())
app.use(cors({ origin: 'http://localhost:3000', credentials: true }))
app.post('auth', (req, res) => {
// fetch user from db, validation, bla bla bla
const token = jwt.sign({ issuer: user.id }, keys.private, { algorithm: 'RS256' })
res.cookie('token', token, { httpOnly: true })
res.json(user)
})
Next.js front-end
const handleSubmit = async (e) => {
e.preventDefault()
try {
const res = await fetch('http://localhost:5000/api/v1/auth', {
method: 'post',
mode: 'cors',
credentials: 'include',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify(formState),
})
const data = await res.json()
console.log(data)
} catch (err) {
console.error(err)
setError(err.message)
}
}
'Twas resolved.
I was looking in Session Storage as opposed to Cookies in my devtools.