I am writing a front-end app and I got stuck on setting token as a cookie in browser.
Back-end is hosted in heroku and front-end is running on localhost
My request from React:
export const axiosConfig: AxiosRequestConfig = {
withCredentials: true,
headers: {
"content-type": "application/json"
},
};
export const login = async (data: UserLoginData): Promise<UserLoginResponse> => {
const res = await axios.post<UserLoginResponse>(API_URL + "/login", data, axiosConfig);
return res.data;
};
Express setup:
app.use(cookieParser());
app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
///
...
///
export const login = async (request: Request, res: Response, next: NextFunction): Promise<void> => {
try {
if (isBodyEmpty(request))
throw new Error();
const { email, password } = request.body;
if (!(email && password)) {
sendFailResponse(res, 400, "All input is required");
return;
}
const user = await User.findOne({ email });
if (user && (await bcrypt.compare(password, user.password))) {
user.token = await createToken(user._id, email);
res.cookie("token", user.token, { maxAge: 900000, httpOnly: true });
res.status(200).send(constructResponse("Success", user));
return next();
}
sendFailResponse(res, 400, "Invalid Credentials");
} catch (error) {
sendFailResponse(res, 400, error.message);
}
};
Response from postman:
Back-end repo: https://github.com/simsta6/botique
Based on the Axios documentation the axios config for credentials is withCredentials: true instead of credentials: 'include' (as defined for fetch). I did not confirm this is the cause of your issue.
Note that you cannot access cookies in the front-end if you set them with the HttpOnly option (MDN).
Note that you can never access the Set-Cookie header on the client-side. This header is listed as forbidden and is filtered out by web-browsers.
Related
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.
If API is accessed as GET request from the browser as authenticated, you get the data.
But when you want to access the data by fetching in client-side by the function getserverside gives me the error that is not authenticated which it fact it is.
API
export default async (req, res) => {
const { method } = req;
const cookie = {
headers: {
cookie: req.headers["cookie"],
},
};
const session = await getSession({ req: cookie });
if (!session) {
return res.status(401).json({
success: false,
message: "NOT Authorized",
});
}
switch (method) {
case "GET":
try {
const jobs = await prisma.jobs.findMany({
where: {
userId: session.id,
},
});
return res.status(200).json({ success: true, jobs });
} catch (error) {
return res.status(404).json({
success: false,
message: error.message,
});
}
Component
export async function getServerSideProps({ req, res }) {
console.log(req.cookies); // Logs all cookies from the request
const cookie = {
headers: {
cookie: req.headers["cookie"],
},
};
const session = await getSession({ req: cookie });
console.log(session); // session is received perfectly.
const res = await axios.get(`${process.env.API_URL}/company/jobs`); // ERROR NOT AUTHENTICATED
}
It is a bit weird because when I try to access the data the cookie is received but when I make the request it says is not authenticated
According to documentation, you should pass to getSession function whole original req in API. and whole context prop in getServerSideProps
https://next-auth.js.org/getting-started/client#getsession
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
I have been struggling for days with no success with my web application. The kind of similar questions on stake overflow are not addressing my issue. I have a standalone Nodejs server which is basically an API provider and a standalone Nextjs web app which serves as the UI. The idea is to have them different Servers in production. On localhost the Next app is running on port 3000 and the Node App is running on port 5000. I am trying to do authentication but how can I verify cookies from the browser (Nextjs App) on the Server. When a user logs in the server sends the user data and a token in a cookie. The cookie is then saved on the browser, from here how can I send back that cookie to the server for verification from Nextjs.
Here is my backend code (Nodejs App running at port 5000)
const signToken = (id) =>
jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
});
const createSendToken = (user, statusCode, res) => {
const token = signToken(user._id);
const cookieOptions = {
expires: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
httpOnly: true,
};
if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;
res.cookie('jwt', token, cookieOptions);
// Remove password from output
user.password = undefined;
user.passwordChangedAt = undefined;
res.status(statusCode).json({
status: 'success',
token,
data: {
user,
},
});
};
exports.Login = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
//1) Check if email and password exists
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))) {
return next(new AppError('Incorrect email or password', 401));
}
//3) If everything is ok , send token to client
createSendToken(user, 200, res);
});
Frontend Code (Nextjs App running on port 3000)
const postData = async (url, post, token) => {
try {
const res = await fetch(`${serverUrl}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
body: JSON.stringify(post),
});
const data = await res.json();
return data;
} catch (error) {
return error;
}
};
const handleSubmit = async(e) => {
e.preventDefault();
const res = await postData('users/login', userData);
console.log(res);
if(res.status === 'fail') return errorAlert(res.message)
successAlert('Login Successful')
dispatch({ type: 'AUTH', payload: {
token: res.token,
user: res.user,
cookie: cookieData
}})
Cookie.set('token', res.token)
localStorage.setItem('firstLogin', true)
};
The cookie which you set after authentication in the browser is included by the browser for subsequent requests to Nextjs server automatically.
You can verify this by checking the req/res in Network Tab.
In Nextjs, we can have a function for validation:
import { NextRequest } from 'next/server'
const TOKEN = '<token-name>';
const isRequestValid = async (req: NextRequest) => {
const cookie = req.cookies[TOKEN]
if (!cookie) {
return false;
}
// validate cookie here by sending request to Node server (running on 5000)
// and return true or false
return ...
}
Then create a _middleware.tsx under pages. Here we will define our _middleware function.
This would run on every req (since it's defined at the root level. More on here)
import { NextRequest, NextResponse } from 'next/server'
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
// check for authenticated routes
if (pathname === '<path-for-which-you-want-to-authenticate>') {
if (!isRequestValid(req)) {
return NextResponse.redirect('<url-for-redirection-if-not-authenticated>')
}
}
// if user is authenticated this will be called which continues the
// normal flow of application
return NextResponse.next()
}
I am trying to learn backend and frontend and so far I was okey. But now I have a problem.
In my backend which I use Express, I send cookie with res.cookie as shown below
auth.js (/auth route)
const register = asyncErrorWrapper(async (req, res, next) => {
const {name, email, password} = req.body;
const user = await User.create({
name,
email,
password
});
sendJwtToClient(user, res);
});
tokenHelper.js
const sendJwtToClient = (user, res) => {
// user modeline gore jwt olusturma
const token = user.generateJwtFromUser();
const {JWT_COOKIE, NODE_ENV} = process.env;
return res
.status(200)
.cookie('access_token', token, {
httpOnly:true,
expires: new Date(Date.now() + parseInt(JWT_COOKIE) * 1000 * 60),
secure: NODE_ENV === 'development' ? false : true
})
.json({
success: true,
access_token: token,
data: {
name: user.name,
email: user.email
},
message: 'Your registration is succesful!'
});
};
Then I get response with Angular app in register.component which is connected with Auth Service
from Register.component
registerUser() {
this.sendButton.nativeElement.disabled = true;
this._authService.registerUser(this.user).subscribe(
res => {
if(res.status === 200) {
console.log(res);
}
}, err => {
console.log(err);
this.showError.nativeElement.className += ' alert alert-danger';
this.errMessage = err.error.message;
this.sendButton.nativeElement.disabled = false;
})
}
Auth Service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class AuthService {
_registerURI = 'http://localhost:3000/auth/register';
_loginURI = 'http://localhost:3000/auth/login';
constructor(private http: HttpClient) { }
registerUser(user) {
return this.http.post<any>(this._registerURI, user, {observe: 'response'});
};
loginUser(user) {
return this.http.post<any>(this._loginURI, user, {observe: 'response'});
};
};
Well I get this response when I click register button: response
Btw I use cors package on backend. Thanks in advance.
I can also share other codes, pages whatever you want. I just thought that this is enough.
Okey, got it.
The problem occurred because of the cors policy. To send cookies to the front-end you need to let in cors package.
Here is the options of cors package that used by express:
app.use(cors( {
origin: 'http://localhost:4200',
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
} ));
And this is the Auth Service method posting registration
registerUser(user) {
return this.http.post<any>(this._registerURI, user, {observe: 'response', withCredentials: true});
};