I spent a long time trying figure it out why it's not working.
I'm implementing a login page using react.
This page send the user and pass to backend (nodejs + express) using axios:
const login = useCallback(e => {
e.preventDefault()
fetch(process.env.REACT_APP_HOST + '/login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: e.target.elements.username.value,
password: e.target.elements.password.value
})
})
.then(response => {
if (response.ok) {
return response.json()
} else if (response.status === 401) {
throw new Error('Invalid user or pass.')
} else {
throw new Error('An error ocurred')
}
...
})
}, [])
In backend, I have a route that receive those data and check on ldap system. So I generate a token using JWT and save it on token
const express = require('express'),
passport = require('passport'),
bodyParser = require('body-parser'),
jwt = require('jsonwebtoken'),
cors = require('cors')
cookieParser = require('cookie-parser')
LdapStrategy = require('passport-ldapauth');
let app = express();
app.use(cookieParser())
app.use(cors());
....
app.post('/login', function (req, res, next) {
passport.authenticate('ldapauth', { session: false }, function (err, user, info) {
const token = jwt.sign(user, env.authSecret)
res.cookie('cookie_token', token) //it doesn't set the cookie
if (err) {
return next(err)
}
if (!user) {
res.sendStatus(401)
} else {
return res.status(200).send({ firstName: user.givenName});
}
})(req, res, next);
});
The problem is that the token is empty, it's not being set.
Couple of things. In your react fetch post method you need to add
withCredentials: true,
beside the httpheader.
fetch(process.env.REACT_APP_HOST + '/login', {
method: 'POST',
withCredentials: true,
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: e.target.elements.username.value,
password: e.target.elements.password.value
})
})
After that in your nodejs part you are using cors but validating all origin. Thats not going to work with credentials. You need to use cors like this to validate specific origin and also turn credentials to true-
app.use(cors({credentials: true, origin: 'http://localhost:4200'}));
after that you can send your cookie by
res.cookie('cookie_token', token, { maxAge: 900000 })
This way the cookie will arrive and once the cookie is arrived in client side you can retrieve the cookie with document.cookie or with any other package like "js-cookie"
Related
I have been dealing with this issue where I am attempting to make a get request to a third-party API using Axios in my Node.js server. The endpoint requires a username and password which I am passing along as follows:
export const getStream = async(req, res) => {
let conn = createConnection(config);
let query = `SELECT * FROM cameras WHERE id = ${req.params.id}`
conn.connect();
conn.query(query, async (error, rows, _) => {
const camera = rows[0];
const {ip, user, pass} = camera;
if (error) {
return res.json({ "status": "failure", "error": error });
}
const tok = `${user}:${pass}`;
const userPass = Buffer.from(tok)
const base64data = userPass.toString('base64');
const basic = `Basic ${base64data}`;
const result = await axios({
method: 'get',
url: `<API URL>`,
headers: {
'Authorization': basic,
'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary'
},
auth: {username: user, password: pass}
})
res.json(result)
});
conn.end();
}
I am then calling this endpoint in my React front-end as such:
const getStream = async () => {
try {
const result = await publicRequest.get(`camera/getStream/${id}`)
console.log(result)
} catch (error) {
console.error(error)
}
}
Each time I make this request, my node server crashes and I get a 401 unauthorized error in my console. It appears that my Authorization header is not getting passed to the server even though everything else gets passed along as so.
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'multipart/x-mixed-replace; boundary=--myboundary',
'User-Agent': 'axios/0.26.1'
},
method: 'get',
url: '<url>',
auth: { username: '<username>', password: '<password>' },
data: undefined
For extra information, this is how my node server is setup
import express, { urlencoded, json } from 'express';
import userRoute from './routes/userRoute.js';
import cameraRoute from './routes/cameraRoute.js';
import cors from 'cors';
const app = express();
app.use(cors());
app.options('*', cors());
app.use(json())
app.use(urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/api/user', userRoute);
app.use('/api/camera', cameraRoute);
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
I have been working on this issue for several days and each time I try something new, I always get a 401 error, and the server crashes.
Any suggestions would be greatly appreciated.
I only see cookies storage tab but cannot access them via my code
When I console.log this token and id it results in "undefined" but in my browser storage cookies are shown
const cookieParser = require('cookie-parser')
const express = require('express');
const app = express()
app.use(cookieParser())
app.get('/', async (req, res) => {
function getLinkedinId(req) {
return new Promise((resolve, reject) => {
const url = 'https://api.linkedin.com/v2/me';
const headers = {
'Authorization': 'Bearer ' + req.cookies.token,
'cache-control': 'no-cache',
'X-Restli-Protocol-Version': '2.0.0'
};
request.get({url: url, headers: headers}, (err, response, body) => {
if(err){
reject(err);
}
resolve(JSON.parse(body).id);
})
})
}
const id = await getLinkedinId(req);
res.cookie('id', id)
console.log(`id: ${req.cookies.id}`)
res.redirect('http://localhost:8080/about')
});
This is because you won't have that cookie coming from the client right after you're setting it. The concept is that you set a cookie on a response, and when the client sends another request it would attach the cookie to that.
In your case the cookie would be available on the subsequent requests, imagine this scenario: Someones logs in, the response will have a cookie attached and on the subsequent call to a private page the cookie gets evaluated:
app.post("/login", (req, res) => {
res
.writeHead(200, {
"Set-Cookie": "token=encryptedstring; HttpOnly",
"Access-Control-Allow-Credentials": "true"
})
.send();
});
app.get("/private", (req, res) => {
if (!req.cookies.token) return res.status(401).send();
res.status(200).json({ secret: "Welcome to the private page" });
});
I have a express server running on localhost:5000 and client running on port 3000.
After sending post request from client to login using fetch API browser is not setting cookie. I'm getting cookie in response header as 'set-cookie' but it isn't set in the browser at client side.
Here is my fetch request code:
return (
fetch(baseUrl + "users/login", {
method: "POST",
body: JSON.stringify(User),
headers: {
"Content-Type": "application/json",
},
credentials: "same-origin",
})
Server side code:
router
.route("/login")
.options(cors.corsWithOptions, (req, res) => {
res.sendStatus(200);
})
.post(cors.corsWithOptions, (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) return next(err);
if (!user) {
res.statusCode = 401;
res.setHeader("Content-Type", "application/json");
res.json({ success: false, status: "Log In unsuccessful", err: info });
}
req.logIn(user, (err) => {
if (err) {
res.statusCode = 401;
res.setHeader("Content-Type", "application/json");
// res.header("Access-Control-Allow-Credentials", true);
res.json({
success: false,
status: "Login Unsuccessful!",
err: "Could not log in user!",
});
}
var token = authenticate.getToken({ _id: req.user._id });
res.cookie("jwt-token", token, {
signed: true,
path: "/",
httpOnly: true,
});
res.statusCode = 200;
res.setHeader("Content-Type", "application/json");
res.json({ success: true, status: "Login Successful!", token: token });
});
})(req, res, next);
});
How to handle Server-side session using Express-session
Firstly you will need the following packages
npm i express-session connect-mongodb-session or yarn add express-session connect-mongodb-session
Now that we have packages that we need to setup our mongoStore and express-session middleware:
//Code in server.js/index.js (Depending on your server entry point)
import expressSession from "express-session";
import MongoDBStore from "connect-mongodb-session";
import cors from "cors";
const mongoStore = MongoDBStore(expressSession);
const store = new mongoStore({
collection: "userSessions",
uri: process.env.mongoURI,
expires: 1000,
});
app.use(
expressSession({
name: "SESS_NAME",
secret: "SESS_SECRET",
store: store,
saveUninitialized: false,
resave: false,
cookie: {
sameSite: false,
secure: process.env.NODE_ENV === "production",
maxAge: 1000,
httpOnly: true,
},
})
);
Now the session middleware is ready but now you have to setup cors to accept your ReactApp so to pass down the cookie and have it set in there by server
//Still you index.js/server.js (Server entry point)
app.use(
cors({
origin: "http://localhost:3000",
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true,
})
);
Now our middlewares are all setup now lets look at your login route
router.post('/api/login', (req, res)=>{
//Do all your logic and now below is how you would send down the cooki
//Note that "user" is the retrieved user when you were validating in logic
// So now you want to add user info to cookie so to validate in future
const sessionUser = {
id: user._id,
username: user.username,
email: user.email,
};
//Saving the info req session and this will automatically save in your mongoDB as configured up in sever.js(Server entry point)
request.session.user = sessionUser;
//Now we send down the session cookie to client
response.send(request.session.sessionID);
})
Now our server is ready but now we have to fix how we make request in client so that this flow can work 100%:
Code below: React App/ whatever fron-tend that your using where you handling logging in
//So you will have all your form logic and validation and below
//You will have a function that will send request to server
const login = () => {
const data = new FormData();
data.append("username", username);
data.append("password", password);
axios.post("http://localhost:5000/api/user-login", data, {
withCredentials: true, // Now this is was the missing piece in the client side
});
};
Now with all this you have now server sessions cookies as httpOnly
I am building a login system using express for node.js and react.js. In my back-end when a user logs in, it creates a cookie. When I go to Network > Login I can see this:
Set-Cookie:
user_id=s%3A1.E%2FWVGXrIgyXaM4crLOoxO%2Fur0tdjeN6ldABcYOgpOPk; Path=/; HttpOnly; Secure
But when I go to Application > Cookies > http://localhost:3000, there is nothing there. I believe that is because I am not allowing credentials to go through correctly when I do a post request from the client side. How do I go about this? Please, let me know if I can improve my question in any way.
//Login back-end
router.post('/login', (req, res, next) => {
if(validUser(req.body)) {
User
.getOneByEmail(req.body.email)
.then(user => {
if(user) {
bcrypt
.compare(req.body.password_digest, user.password_digest)
.then((result) => {
if(result) {
const isSecure = process.env.NODE_ENV != 'development';
res.cookie('user_id', user.id, {
httpOnly: true,
secure: isSecure,
signed: true
})
res.json({
message: 'Logged in'
});
} else {
next(new Error('Invalid Login'))
}
});
} else {
next(new Error('Invalid Login'))
}
});
} else {
next(new Error('Invalid Login'))
}
});
//Allow CORS index.js
app.use(
cors({
origin: "http://localhost:3000",
credentials: true
})
);
//Login client side (React.js)
loginUser(e, loginEmail, password) {
e.preventDefault();
let email = loginEmail;
let password_digest = password;
let body = JSON.stringify({ email, password_digest });
fetch("http://localhost:5656/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
credentials: "include",
body
})
.then(response => response.json())
.then(user => {
console.log(user);
});
}
You should be secure of set "credentials" in the server and in app.
Try to set on you index.js or app.js server side this:
app.use(function(req, res, next) {
res.header('Content-Type', 'application/json;charset=UTF-8')
res.header('Access-Control-Allow-Credentials', true)
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
)
next()
})
and in you client site add options like this:
let axiosConfig = {
withCredentials: true,
}
export async function loginUser(data) {
try {
const res = await axios.post(
`${URL}:${PORT}/${API}/signin`,
data,
axiosConfig
)
return res
} catch (error) {
console.log(error)
}
}
Edit
To set "credentials" in server we need this line:
res.header('Access-Control-Allow-Credentials', true)
This would let you handle credentials includes in headers.
You also have to tell to axios to set credentials in headers with:
withCredentials: true
Do not forget to adjust cors middleware.
Your node.js express code
const express = require("express");
const cors = require('cors')
const app = express();
app.use(cors(
{
origin: 'http://localhost:3000',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
));
app.use(function(req, res, next) {
res.header('Content-Type', 'application/json;charset=UTF-8')
res.header('Access-Control-Allow-Credentials', true)
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
)
next()
})
app.get("/auth", function(req, res){
res.cookie('token', 'someauthtoken')
res.json({id: 2});
});
app.listen(3030);
Your front-end code
import React, { useEffect } from 'react';
import axios from 'axios';
async function loginUser() {
try {
const res = await axios.get(
'http://localhost:3030/auth',
{
withCredentials: true,
}
)
return res
} catch (error) {
console.log(error)
}
}
function App() {
useEffect(() => {
loginUser();
}, [])
return (
<div>
</div>
);
}
export default App;
It is because you set httpOnly: true.
This will block the visibility to client side, like reading from javaScript document.cookie().
You can solve this by turn it off.
If you can't see your cookie in the browser, I think it is because you're setting hhtpOnly to true in the cookie's options.
cookie.httpOnly
Specifies the boolean value for the HttpOnly Set-Cookie attribute. When truthy, the HttpOnly attribute is set, otherwise it is not. By default, the HttpOnly attribute is set.
Note: be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie
res.cookie('user_id', user.id, {
httpOnly: false, // try this
secure: isSecure,
signed: true
})
You need to configure cors in your backend server first.
First, install cors using npm i cors then in your express server add this line of code:
app.use(cors({
origin: "YOUR FRONTEND SITE URL HERE",
credentials: true,
}));
Then, in your frontend app where you are sending GET/POST requests to your backend, make sure to add in your request
If you've used fetch:
const res = await fetch('BACKEND SERVER URL', {
credentials: "include",
// other objects
});
If axios is used:
const res = await axios.post('BACKEND SERVER URL',
{ withCredentials: true },
// other objects,
);
This will solve the problem of storing cookies in frontend sent from backend.
I am using node 6.5.0 and npm 3.10.3.
I'm getting this invalid csrf token error when I am trying to log in the user to the site.
{ ForbiddenError: invalid csrf token
at csrf (/Users/Documents/web-new/node_modules/csurf/index.js:113:19)
The login with storing session in redis works without the csurf module (https://github.com/expressjs/csurf). With the csurf module, the session ID is getting stored in redis but I am not able to return the proper response to the client to log in the user. I am using Angular2 with node/express. From what I understand, Angular2 by default supports CSRF/XSRF with the CookieXSRFStrategy when using HTTP service, so all I need to do is configure something on the node/express side. The Angular2 app with webpack-dev-server is running on localhost:3000 while the node/express server is running on localhost:3001. I am supporting CORS.
I am able to see cookie with name XSRF-TOKEN in devtools at localhost:3000.
Could you kindly recommend how I might fix this error?
//cors-middleware.js
var corsOptions = {
origin: 'http://localhost:3000',
credentials:true
}
app.use(cors(corsOptions));
app.use(function(req, res, next) {
res.setHeader('Content-Type','application/json');
next();
})
};
//index.js
import path from 'path';
import session from 'express-session';
import connectRedis from 'connect-redis';
import rp from 'request-promise';
import * as _ from 'lodash';
import cors from 'cors';
import csurf from 'csurf';
const redisStore = connectRedis(session);
const dbStore = new redisStore(db);
let baseUrl = app.getValue('baseUrl');
/* ~~ api authentication ~~ */
let options = {
method: 'POST',
url: `${baseUrl}/authenticate`,
rejectUnauthorized: false,
qs: {
username: 'someUsername', key: 'someKey'
},
json: true
};
rp(options)
.then(response => {
let apiToken = response.response;
app.setValue("token", apiToken);
})
.catch(err => {
console.error(err);
});
/* ~~ configure session ~~ */
app.use(session({
secret: app.getValue('env').SESSION_SECRET,
store: dbStore,
saveUninitialized: false,
resave: false,
rolling: true,
cookie: {
maxAge: 1000 * 60 * 30 // in milliseconds; 30 min
}
}));
/* ~~ login user ~~ */
let csrf = csurf();
app.post('/loginUser', csrf, (req, res, next) => {
let user = {};
let loginOptions = {
method: 'POST',
url: `${baseUrl}/client/login`,
rejectUnauthorized: false,
qs: {
token: app.getValue('token'),
email: req.body.email,
password: req.body.password
},
headers: {
'Content-Type': 'application/json'
},
json: true
};
rp(loginOptions)
.then(response => {
let userToken = response.response.token;
let clientId = response.response.clientId;
req.session.key = req.session.id;
user.userToken = userToken;
user.clientId = clientId;
let clientAttributeOptions = {
url: `${baseUrl}/client/${clientId}/namevalue`,
rejectUnauthorized: false,
qs: {
token: app.getValue('token'),
usertoken: userToken
},
json: true
};
return rp(clientAttributeOptions);
})
.then(response => {
req.session.user = user;
res.send({user:user})
})
.catch(err => {
next(err);
})
});
My issue was that I was including the csrf function as a middleware only in the app.post('/loginUser) route.
When I included it for all routes, the module worked fine.
let csrf = csurf();
app.get('/*', csrf, (req, res) => {
res.sendFile(app.get('indexHTMLPath'));
});