I've working with jwt and cookies I don't why my cookie does not set in my browser.
If I do this in postman works properly, create cookie and looks well, but when I do this in my browser with my react form, cookie does not set.
Please any advice
This my frontend code
const onSubmit = useCallback(async (e) => {
e.preventDefault()
setStatus('pending')
setError(null)
const credentials = {
email: values.email,
password: values.password
}
console.log(credentials)
try {
const res = await API.post(LOGIN_API, credentials)
console.log(res.data)
if (res.data.success) {
setStatus('success')
navigate('/')
} else {
setStatus('error')
}
} catch (err) {
setError(error)
}
}, [values, error, navigate])
my backend
const login = async (req, res) => {
const text = 'SELECT user_id, password, email FROM user_account WHERE email=$1;'
const values = [req.body.email]
try {
const response = await client.query(text, values)
const match = await bcrypt.checkPassword(req.body.password, response.rows[0].password)
if (match) {
res.cookie("access_token", jwt.sign({ id: response.rows[0].user_id }, jwtSecret), { httpOnly: true })
res.status(200).json({ success: true, message: "Logged in successfully" })
} else {
res.status(401).json({ success: false, message: "Credentials are not valid" })
}
} catch (err) {
console.log(err.stack)
}
}
And then here my axios instance
import axios from 'axios'
export default axios.create({
baseURL: process.env.REACT_APP_BASE_API_URL
})
Cookies can only be set if you load a resource by a browser, no through JavaScript. There is no way to set httponly cookie this way.
Usually jwt tokens are stored in localStorage
Related
My application has a login page that works fine when the user enters the correct login credentials but while test casing for eventual wrong entries of either usernames or passwords I've realized that my catch block isn't able to correctly format the error object from the backend thus nothing is being rendered to the frontend.
I've tried using res.send(401).json({"message":"Unauthorized"}); in the backend instead of res.sendStatus(401); but the former method doesn't trigger an error response and rather returns as a response in the fetch.
While using res.sendStatus(401);, although the error is triggered my catch block isn't able to render it's response.
The backend:
const User = require('../model/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const handleLogin = async (req,res) => {
const user = req.body.user.toLowerCase();
const pwd = req.body.pwd;
if(!user || !pwd) return res.sendStatus(400);
const foundUser = await User.findOne({username: user}).exec();
if(!foundUser) return res.sendStatus(401);
const match = await bcrypt.compare(pwd, foundUser.password);
console.log(match);
if(match){
const roles = Object.values(foundUser.roles);
const accessToken = jwt.sign(
{"userInfo": {
"username": foundUser.username,
"roles": roles
}},
process.env.ACCESS_TOKEN,
{expiresIn: "300s"}
);
const refreshToken = jwt.sign(
{"username": foundUser.username},
process.env.REFRESH_TOKEN,
{expiresIn: "1d"}
);
foundUser.refreshToken = refreshToken;
const result = await foundUser.save();
if(!result) return res.status(500);
res.cookie("jwt",refreshToken,{httpOnly: true, sameSite: "None", maxAge: 24*60*60*1000});
res.json({user, roles, accessToken});
}
else{
res.sendStatus(401);
}
}
module.exports = {handleLogin};
The fetch:
fetch(BASE_URL + "/login", {
method: "POST",
headers: {
"Content-Type":"application/json"
},
body: JSON.stringify({user: username,pwd})
})
.then(res => res.json())
.then(data => {
setUser(data);
console.log(data);
})
.then(() => {
setSuccess(true);
setTimeout(() => {
navigate("/");
}, 1000);
})
.catch((err)=>{
console.log(err);
if(err.status == "401"){
setErrMsg("Wrong username or password.")
}
else{
setErrMsg("Login failed, try again.")
}
errRef.current.focus();
})
Once the error is triggered the console displays the following error SyntaxError: Unexpected token 'U', "Unauthorized" is not valid JSON and in addition to that the error is not rendered to the frontend.
How can I correctly format the response from the backend or handle the error response from the front end to be able to correctly render it to the view?
Your first then assumes that the response has valid json. Instead it should check if the response status is ok and if not, throw an error that will be caught by the catch.
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error(res.status);
})
i´m creating a Authentication page with React and Express. I'm using JWT too.
I´ve made this route in the back:
server.js
...
app.use(
cookieSession({
name: "prode_session",
secret: "MIOURI_PRODE_SECRET", //add to .env variable
httpOnly: false,
})
);
app.use(cors());
...
auth.routes.js
app.post("/signin", controller.signin);
user.routes.js
app.get(
"/user",
[authJwt.verifyToken],
(req, res) => res.send(true)
)
auth.controller.js
exports.signin = async (req, res) => {
const user = await Users.findOne({
where: { email: req.body.email },
});
try {
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
const passwordIsValid = bcrypt.compareSync(
req.body.password,
user.password
);
if (!passwordIsValid) {
return res.status(401).send({
message: "Invalid Password!",
});
}
const token = jwt.sign({ id: user.id }, config.secret, {
expiresIn: 84000, //24hours
});
req.session.token = token;
console.log(req.session);
return res.status(200).send({
isLogged: true,
id: user.id,
email: user.email,
suscripcion: user.suscripcion,
preference_id: user.preference_id,
token,
});
} catch (error) {
console.log(error);
}
};
authJWT.js
verifyToken = async (req, res, next) => {
let token = req.session.token;
console.log(`THIS IS THE TOKEN: ${token}`);
if (!token) {
return res.status(403).send({
message: "No token provided",
});
}
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
console.log(err);
return res.status(401).send({
message: "Unauthorized!",
});
}
req.id = decoded.id;
next();
});
};
const authJwt = { verifyToken };
module.exports = authJwt;
When I test this with POSTMAN, it works Ok, I mean, if first I try to make the GET request, the response is "No token provided", but if I signin first, generate the token and then make the GET request, I get true.
The problem is when I try to implement this in the front.
I have this Login component in React in which I make a POST request with the credentials:
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch("http://localhost:3000/signin", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
console.log(data);
if (data.isLogged && data.suscripcion === true && data.token) {
await tokenAvailable()
//navigate(`/masthead/${email}&${data.isLogged}&${data.id}`);
} else if (data.isLogged && data.suscripcion === false) {
navigate("/suscripcion", {
state: { preference_id: data.preference_id },
});
} else {
window.alert("Invalid Login");
}
} catch (error) {
console.log(error);
}
};
async function tokenAvailable() {
const user = await fetch("http://localhost:3000/user", {
method: "GET",
mode: "cors",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
const response = await user.json();
setUser(await response);
console.log(await response);
return response;
}
When I make the POST, the GET request is executed (tokenAvailable function) after receiving the response, but I receive "No token Provided" while I expect to receive "true" as in Postman.
From what I debug, the authJWT.js file, is not receiving nothing from the req.session.token.
When I compare the headers from postman and the browser, in postan the SET-cookie key appears, but in the browser not.
postman:
browser:
I need some help here. I´ve been strugling with this for almost 3 days.
I found a solution for this. Apparently, the HttpOnly Cookie approach works if the React app and the back-end server hosted in same domain. So we need to use http-proxy-middleware for local development.
I´ve tried to install the http-proxy-middleware but a lot of errors came, so I decided to store de JWT in the localstorage.
I have a login page and a register page. When I register a user from the frontend form, it creates a new user. But when I try to log in to the user, I get the 200 status and my data back, but I guess it doesn't set the cookie. When I try to go to a protected route that only a logged-in user can access, I get the error I made from the backend which is "You are unauthenticated". How do I send or set the cookie in the front end too? Everything really works fine in the Insomnia app, the cookies get set.
this is how I'm making the post-login request
const submit = async (e) => {
e.preventDefault();
const data = { username, password };
try {
await axios.post(path, data).then((res) => {
console.log(res);
});
} catch (err) {
setLoading(false);
setError(err.message);
}
this is the login controller in the server side.
const login = async (req, res) => {
try {
const oneUser = await Users.findOne({ username: req.body.username });
if (!oneUser) {
return res.status(403).json("No such user in the database");
}
const isPassword = bcryptjs.compare(req.body.password, oneUser.password);
if (!isPassword) {
return res.status(500).json(`${req.body.username} Password is incorrect`);
}
const token = jwt.sign(
{
id: oneUser._id,
isAdmin: oneUser.isAdmin,
},
process.env.jwt
);
const { password, ...others } = oneUser._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json({ ...others });
} catch (err) {
res.status(500).json(err);
}
};
I have a problem with the logged in user, when I refresh the page the user gets lost. This is how I assign a JWT token:
const signToken = id => {
return jwt.sign({ id }, 'my-ultra-secure-and-ultra-long-secret', {
expiresIn: '14d',
});
};
This is how I send a token to a cookie with this function as well:
const createSendToken = (user, statusCode, res) => {
const token = signToken(user._id);
const cookieOptions = {
expires: new Date(Date.now() + 14 * 1000 * 60 * 24),
httpOnly: true,
};
res.cookie('jwt', token, cookieOptions);
// Remove password from output
user.password = undefined;
res.status(statusCode).json({
status: 'success',
token,
data: {
user,
},
});
};
This is my login controller:
exports.login = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
// 1) Check if email and password exist
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400));
}
// 2) Check if user exists && password is correct
const user = await User.findOne({ email }).select('+password');
if (user && (await user.correctPassword(password, user.password))) {
createSendToken(user, 200, res);
} else {
return next(new AppError('Incorrect email or password', 401));
}
});
This is my Protect controller (protect middleware):
exports.protect = catchAsync(async (req, res, next) => {
// 1) Getting token and check of it's there
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return next(
new AppError('You are not logged in! Please log in to get access.', 401)
);
}
// 2) Verification token
const decoded = await promisify(jwt.verify)(
token,
'my-ultra-secure-and-ultra-long-secret'
);
// 3) Check if user still exists
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next(
new AppError(
'The user belonging to this token does no longer exist.',
401
)
);
}
// 4) Check if user changed password after the token was issued
if (currentUser.changedPasswordAfter(decoded.iat)) {
return next(
new AppError('User recently changed password! Please log in again.', 401)
);
}
// GRANT ACCESS TO PROTECTED ROUTE
req.user = currentUser;
res.locals.user = currentUser;
next();
});
This is my private route with this middleware:
router.route('/:id').get(authController.isLoggedIn, postController.getPost);
The problem is when I log in I get a cookie, but I can't access the protected route (I get an error token is undefind). When I refresh the page the user is lost but the cookie remains in storage. When I try to access the protect route via postman and when I add the Authorization Bearer to the header ..... (token) I can access it.
This is my frontend user reducer:
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { loading: true, isAuthenticated: false };
case USER_LOGIN_SUCCESS:
return {
loading: false,
isAuthenticated: true,
user: action.payload,
};
case USER_LOGIN_FAIL:
return { loading: false, isAuthenticated: false, error: action.payload };
case USER_LOGOUT:
return { loading: false, isAuthenticated: false, user: null };
default:
return state;
}
};
This is my user action:
export const login = (email, password) => async dispatch => {
try {
dispatch({
type: USER_LOGIN_REQUEST,
});
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const { data } = await axios.post(
'/api/v1/users/login',
{ email, password },
config
);
dispatch({
type: USER_LOGIN_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: USER_LOGIN_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
This is my login screen:
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const redirect = location.search ? location.search.split('=')[1] : '/';
const dispatch = useDispatch();
const userLogin = useSelector(state => state.userLogin);
const { loading, error, isAuthenticated } = userLogin;
console.log(isAuthenticated);
useEffect(() => {
if (isAuthenticated) {
history.push(redirect);
}
if (error) {
console.log(error);
}
}, [isAuthenticated, history, redirect, error]);
const submitHandler = e => {
e.preventDefault();
dispatch(login(email, password));
};
I stuck here about 2 days trying to complete this. Please someone help me :)
As you are setting the jwt token in a cookie, you can read it from there when making a request. You can send the cookie automatically with axios by adding withCredentials: true in your axios config object, like:
axios({
withCredentials: true
})
On the server side, you can get the jwt cookie value by looking it on the req.cookies object, like:
let token;
if (req.cookies.jwt) {
token = req.cookies.jwt;
}
I am trying to configure my JWT through http-only cookies
const signIn = async (req, res) => {
const { email } = req.body;
try {
const user = await User.findOne({ email });
const access_token = jwt.sign({ id: user._id }, jwtSecret, {
expiresIn: token_expiry_time,
});
const refresh_token = jwt.sign({ id: user._id }, jwtRefreshSecret, {
expiresIn: refresh_token_expiry_time,
});
res.cookie('access_token', access_token, { httpOnly: true });
res.cookie('refresh_token', refresh_token, { httpOnly: true });
return res.status(200).json({
status: true,
data: {
user: {
id: user._id,
},
},
});
} catch (err) {
Server.serverError(res, err);
}
};
but at the client-side it refuses to set the cookie in the browser, it returns the error "cannot set cookie because same-site is not 'None' ", After setting sameSite:'None' on the server side, it then gave the error "same-site set to 'None' needs to have a 'Secure' field", I then set secure:true on the backend but it doesn't work because I am not using https for development.
client-side code
const onSubmit = (cred) => {
setLoading(true);
restAPI
.post('auth/signin', cred)
.then(({ data }) => {
setLoading(false);
setError(false);
console.log(data.data);
})
.catch((err) => {
setLoading(false);
setError({
status: true,
message: err.response.data.message,
});
});
};
how can I solve this?