Please I need a help on how to fetch a data from node to react, I have been stuck here for 2 weeks now.
Here are my backend code:
server.js:
require("dotenv").config();
const app = require("./src/app");
const port = process.env.PORT || 4000;
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(port, () => {
console.log(`Server is running on port http://localhost:${port}`);
});
app.js:
const express = require("express");
const cors = require("cors");
const cookieSession = require("cookie-session");
const app = express();
app.use(
cors({
origin: ["http://localhost:4000/api", "http://localhost:3000"],
})
);
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
app.use(express.json());
app.use(express({ type: "application/vnd.api+json" }));
app.use(express.urlencoded({ extended: true }));
app.use(
cookieSession({
name: process.env.COOKIE_NAME, //ookie name in .env
secret: process.env.COOKIE_SECRET, //secret name in .env
httpOnly: true,
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 24 hours duration before expire
})
);
app.use("/uploads", express.static("uploads"));
const jobRoute = require("./routes/job.routes");
app.use("/api/", jobRoute);
module.exports = app;
service.js:
const db = require("../config/database");
const notificationServices = require("./notification.services");
const { jobReuseQuery } = require("../job reuseable query/job.queries");
const createJob = async (body) => {
const {
title,
salary_type,
salary,
job_types,
description,
company_id,
sector_id,
category_id,
} = body;
const { rows } = await db.query(
`INSERT INTO jobs (title, salary_type, salary, job_types, description, company_id, sector_id, category_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
[
title,
salary_type,
salary,
job_types,
description,
company_id,
sector_id,
category_id,
]
);
notificationServices.sendMatchJobsToUserProfiles(rows[0]);
return rows[0];
};
const getAllJobs = async () => {
const { rows } = await db.query("SELECT * FROM jobs");
return rows;
};
controller.js:
const jobService = require("../services/job.services");
const createJob = async (req, res) => {
try {
const job = await jobService.createJob(req.body);
res.status(201).send({
message: "Job created successfully",
data: job,
});
} catch (err) {
res.status(400).send(err.message);
}
};
const getAllJobs = async (req, res) => {
try {
const jobs = await jobService.getAllJobs();
res.status(200).send({ data: jobs });
} catch (err) {
res.status(400).send({ message: err.message });
}
};
routes.js:
const router = require("express-promise-router")();
const jobController = require("../controllers/job.controller");
const auth = require("../middleware/auth.middleware");
router.post("/jobs", auth, jobController.createJob);
auth.js:
const db = require("../config/database");
const jwt = require("jsonwebtoken");
const dotenv = require("dotenv");
dotenv.config();
const auth = async (req, res, next) => {
const token = req.session.token;
if (!token) {
return res.status(401).send({ error: "Please Authenticate" });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const { rows } = await db.query("SELECT * FROM users WHERE id = $1", [
decoded.id,
]);
if (!rows[0]) {
throw new Error("User not found");
}
req.user = rows[0];
next();
} catch (error) {
return res.status(401).send({ error: error.message });
}
};
module.exports = auth;
React frontend code:
import React, { useEffect } from "react";
import tech from "../../image/tech-big.svg";
import health from "../../image/health-big.svg";
import eng from "../../image/eng-big.svg";
import axios from "axios";
import { useState } from "react";
const Joblist = () => {
const [name, setName] = useState([]);
//first method
const response = axios
.get("http://localhost:4000/api/jobs/")
.then((res) => res.json());
console.log(response);
//second method
const fetchData = async () => {
const newData = await fetch("http:localhost:4000/api/jobs", {
method: "GET",
headers: {
"Content-Type": "application/json",
ACCEPT: "application/json",
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Origin": true,
credentials: "same-origin",
Authorization: `Bearer ${token}`,
},
}).then((res) => res.json());
console.log(newData);
setName(newData.jobs.name);
fetchData();
};
you can see in my react, I have 2 method i used trying to fetch the data fron node to the react
first method return error in my browser console :
Promise {<pending>}
GET http://localhost:4000/api/jobs/ 401 (Unauthorized)
Uncaught (in promise) AxiosError {message: 'Request failed with status code 401', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
while the second method return nothing in my browser console
I am trying to fetch a data from my node backend into frontend react but my first method log error while the second method log nothing
I think you need to clean up a bit your setting, since you're using CORS than you can first make some changes :
// .....
const app = express();
// with CORS you can do all your setting at the same place, so you don't need to set the header
const corsOptions = {
origin: ["http://localhost:4000/api", "http://localhost:3000"],
methods: "GET, POST, PUT, DELETE, OPTIONS, HEAD",
credentials: true, // for jwt/cookie !
};
app.use(cors(corsOptions));
app.use(express.json());
app.use(express({ type: "application/vnd.api+json" }));
app.use(express.urlencoded({ extended: true }));
app.use(
cookieSession({
name: process.env.COOKIE_NAME,
secret: process.env.COOKIE_SECRET,
maxAge: 24 * 60 * 60 * 1000,
httpOnly: true,
sameSite: false, //set this to "None" if you deploy to production on cross domaine.
secure: false, //set to true is required on production with https
});
app.use("/uploads", express.static("uploads"));
const jobRoute = require("./routes/job.routes");
app.use("/api/", jobRoute);
module.exports = app;
Update the fetch part I clean up (I remove the header) and i just notice on your job.controller.js you put data property on your response json.. so you need to check again your database structure if it's still not working.
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("http:localhost:4000/api/jobs", {
credentials: "include", //to be able to send with cookies...
});
if(response.ok) {
const newData = await response.json();
console.log(newData);
setName(newData.data.jobs.name); // this part you need to check your data structure again...
}
} catch (error) {
console.log(error)
}
}
fetchData();
}, []);
Optional note: this part is not part of your question, just in case if there is still issue with the cookie-session and jwtoken, you can change how the JWT is stored in the cookie: cookie-session purpose is to create a "session id" to authenticate the user by storing it at the client side (on the browser, with the cookie), i don't really see the point to use this if you're gonna use jwt token to authenticate anyway ? I let you see the step below if you re still stuck at this part:
First, you may need to install cookie-parser middleware, because if this method work for you, you will be able to uninstall cookie-session.
const cookieParser = require('cookie-parser')
/...
app.use(cookieParser());
on the auth.controllers.js
const loginAuth = async (req, res) => {
try {
const token = await authServices.login(req.body);
// set the jwt token on the cookie
res.cookie("jwt", token, {
maxAge: 24 * 60 * 60 * 1000,
httpOnly: true,
sameSite: false, //set this to "None" if you deploy to production on cross domaine.
secure: false, //set to true is required on production with https
})
return res.status(200).json({
//controller will return this message if the body sent was match
message: "User logged in successfully!",
});
} catch (error) {
//ratther it will return this erroe message
return res.status(500).json({ message: error.message });
}
};
//create a logout session for the user to logout by signing session to null
const logoutAuth = async (req, res) => {
res.clearCookie("jwt")
return res.status(200).send({ message: "User logged out successfully!" });
};
You also need to replace const token = req.session.token; in your activeAuth function, and in your auth.middleware.js at the auth middleware function by this:
const token = req.cookies["jwt"] //or
const token = req.cookies.jwt
Finally if it work you can uninstall cookie-session.
I am having troubles understanding how to protect an API with next-auth using Google authentication, I have searched a lot online but there is not a good guide on how to achieve this, at the moment I have created the [...nextauth].js as follows:
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { MongoDBAdapter } from "#next-auth/mongodb-adapter"
import connection from "../../../lib/database";
const options = {
secret: process.env.JWT_SECRET,
adapter: MongoDBAdapter(connection),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
pages: {
signIn: '/signin'
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, user, token }) {
return session;
},
async jwt({ token, user, account, profile, isNewUser }) {
return token;
}
}
}
export default (req, res) => NextAuth(req, res, options)
The effect I am trying to achieve is to send the accessToken to the api on each request, when the api receives the request validates the token and if the token is ok then sends the response otherwise redirects the user to the /signin page.
I believe I should add the token in the axios Bearer in the signin callback am I right? However I have no idea on how to write the middleware that checks if the jwt token is correct.
I have a request for authorization to the server:
axios
.post(
"http://localhost:5000/api/auth/sign-in",
{ ...values },
)
.then((res) => {
const { token } = res.data;
//there I need to save coookie with token that came from server
})
after successful authorization, the server sends the token, how can I save this token in the cookie with the httpOnly flag?
You can use universal-cookie package to do this: https://www.npmjs.com/package/universal-cookie
import Cookies from 'universal-cookie';
const cookies = new Cookies();
...
.then((res) => {
const { token } = res.data;
cookies.set('token', token , { httpOnly: true, path: "/" });
})
After reading the following post, https://dev.to/chrsgrrtt/easy-user-authentication-with-next-js-18oe and consulting the following question Using next-iron-session's "withIronSession" with Next.JS to perform simple authentication, I am still unable to access the session using req.session.get('user'). Below is my implementation in a Next.js project:
Create a util
import {withIronSession} from 'next-iron-session';
const cookie = {
cookieName: process.env.COOKIE_NAME,
password: process.env.COOKIE_SECRET,
cookieOptions: {secure: process.env.NODE_ENV === 'production'},
};
export const guard = (handler) => {
return withIronSession(handler, cookie);
};
export default cookie;
Create an API endpoint
const zlib = require('zlib');
import cookie from '#/utils/cookie';
const fetch = require('node-fetch');
import {withIronSession} from 'next-iron-session';
export default withIronSession(
async (req, res) => {
if (req.method === 'POST') {
try {
const request = await fetch(
process.env.NEXT_PUBLIC_API_BASE_URL + '/api/login',
{
method: 'post',
body: req.body,
headers: {
'Content-Type': 'application/json',
'Origin': req.headers.host || req.headers.origin,
},
}
);
const response = await request.text();
const {success, data, message} = JSON.parse(response);
// set JWT in session
compressor(data, (x) => req.session.set('user', x));
// persist session value
await req.session.save();
// console.log(req.session.get('user'));
return res.status(201).json({success, message});
} catch (error) {
console.log(error);
}
}
return res.status(404).json('Not found');
},
cookie
);
Access session data in a page
export const getServerSideProps = guard(async (ctx) => {
const {req} = ctx;
const session = req.session.get();
console.log({session});
return {redirect: {destination: '/sign-in', permanent: false}};
});
The above terminal log gives an empty object. Is there something am doing wrong??
Try the following:
export const getServerSideProps = guard(async function ({
req,
res,
query,
}) {
//Assuming you have "user" session object
const user = req.session.get("user");
...
});
Harel
import Cookies from 'universal-cookie';
const cookies = new Cookies();
cookies.remove('abc');
console.log(cookies.getAll());
It is still printing my abc cookie.
May be you need to do something like
cookies.remove('abc', { path: '/' });
More info here
Cookies need to have both path and domain appended to them to be removed. Try this:
cookies.remove("abc", {path: "/", domain: ".example.com"})
If you are setting the cookie on a response in a login route/controller in express backend for JWT and are using 'httpOnly' option, you are unable to access the token from the client/react, even when using a third party library like 'universal-cookie' or 'document.cookie'.
You will need to clear the cookie on the response from the backend e.g. when a user logs out in the logout controller as detailed below.
Front-end:
// React redux logout action
export const logout = () => async (dispatch) => {
try {
await axios.get('/api/auth/logout')
localStorage.removeItem('userInfo')
dispatch({ type: type.USER_LOGOUT })
} catch (error) {
console.log(error)
}
}
Backend:
const User = require('../../models/userModel')
const generateToken = require('../../utils/generateToken')
// #desc Auth user & get token
// #route POST /api/auth/login
// #access Public
const login = async (req, res) => {
const { email, password } = req.body
try {
const user = await User.findOne({ email })
if (user && (await user.verifyPassword(password))) {
let token = generateToken(user._id)
res.cookie('token', token, {
maxAge: 7200000, // 2 hours
secure: false, // set to true if your using https
httpOnly: true,
})
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: token,
})
} else {
res
.status(401)
.json({ success: false, message: 'Invalid email or password' })
}
} catch (error) {
res.status(500).json({ success: false, message: error.toString() })
}
}
// #desc Logout controller to clear cookie and token
// #route GET /api/auth/login
// #access Private
const logout = async (req, res) => {
// Set token to none and expire after 1 seconds
res.cookie('token', 'none', {
expires: new Date(Date.now() + 1 * 1000),
httpOnly: true,
})
res
.status(200)
.json({ success: true, message: 'User logged out successfully' })
}
module.exports = {
login,
logout,
}
I just add this for people who may have similar problem in future, just like I had today. This may be an issue with asynchronous actions. Setting, removing cookies is asynchronous.