I have a small API made in nodejs with express. A while ago I did not touch it and everything worked perfectly. Only now have I decided to implement JsonWebToken. In Postman, the login works fine, however, when trying to send the token as a header in a request I get an error. When i don't send the token in the request, response successfull (obviously since there is no token, the endpoint returns a 401 to me).
If I try to do it after authenticating (saving the token in an environment variable) and this time assigning it to the header, the following happens
If I send anything if it works, apparently it has to do with the length of the token.
I have tried it outside of postman, and the same thing happens, so the error does not seem to be from postman.
I don't know how to solve the problem, apparently nodejs does not handle the request by the length of the token.Is there a way to expand that?
The nodejs server entry point is:
// Enviroment process
require("dotenv").config();
// Body Parser
const bodyParser = require("body-parser");
const cors = require("cors");
// Express server
const app = require("express")();
app.use(cors());
// BodyParser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Routes middleware
app.use(require("./routes/index"));
// Run server
app.listen(process.env.PORT, () => {
console.log(`Escuchando en el puerto ${process.env.PORT}`);
});
Routes:
const express = require("express");
const { checkToken } = require("../middlewares/authentication");
const app = express();
/// Base Routes
app.get(
"/equipments",
[checkToken],
require("../controllers/equipment/get_all.controller")
);
module.exports = app;
The checkToken middleware:
const jwt = require("jsonwebtoken");
const checkToken = (req, res, next) => {
const token = req.get("token") || req.query.token;
jwt.verify(token, process.env.SEED, (err, decoded) => {
if (err) {
return res.status(401).json({
ok: false,
error: {
message: "Invalid Token",
},
});
}
req.user = decoded.user;
next();
});
};
module.exports = {
checkToken,
};
The .env variables:
// Node env (development | production)
NODE_ENV=development
// Server Port
PORT=3000
// Token Time Expiration
TOKEN_EXPIRES=48h
// Token Seed
SEED=exampleseed
UPDATE
When I send the token through the body of the request, the error does not occur, and everything works correctly (obviously changing the middleware so that it receives it by the body). The problem is when I send it by headers or a query parameter.
const checkToken = (req, res, next) => {
// const token = req.get("token") || req.query.token;
const token = req.body.token;
jwt.verify(token, process.env.SEED, (err, decoded) => {
if (err) {
return res.status(401).json({
ok: false,
error: {
message: "Invalid Token",
},
});
}
req.user = decoded.user;
next();
});
};
UPDATE AND SOLUTION:
After trying only those files, I realized that the error did not come from these. The problem was in the authentication. When creating the token I used the information of the logged in user, however, I had not realized that it had a base64 image field.
// ... after login, user object contains object information
let token = jwt.sign(
{
user: {
id: user.id,
name: user.name,
image: user.image.base64Url
},
},
process.env.SEED,
{ expiresIn: process.env.TOKEN_EXPIRES }
);
The length of the base64 image made the token extremely long. Then when making the request and sending a string token with many characters, the reading error occurrs (Error: read ECONNRESET).
The solution was to ignore the image field when creating the token.
Finally, before an error of the same type, check that a field that contains too much information is not being sent.
Related
I have an api in express js that stores token in cookie on the client-side (react). The cookie is generated only when the user logins into the site. For example, when I test the login api with the postman, the cookie is generated as expected like this:
But when I log in with react.js then no cookie is found in the browser. Looks like the cookie was not passed to the front end as the screenshot demonstrates below:
As we got an alert message this means express api is working perfectly without any error!!
Here is my index.js file on express js that includes cookie-parser middleware as well
require("dotenv").config();
const port = process.env.PORT || 5050;
const express = require("express");
const app = express();
const cors = require("cors");
const authRouter = require("./routes/auth");
var cookieParser = require('cookie-parser')
connect_db();
app.use(express.json());
app.use(cookieParser())
app.use(cors());
app.use("/" , authRouter);
app.listen(port , () => {
console.log("Server is running!!");
})
Code for setting up the cookie from express api only controller
const User = require("../models/user");
const jwt = require("jsonwebtoken");
const bcrypt = require('bcrypt')
const login = async (req, res) => {
const { email, password } = req.body;
try {
const checkDetails = await User.findOne({ email });
if (checkDetails) {
const { password: hashedPassword, token, username } = checkDetails;
bcrypt.compare(password, hashedPassword, function (err, matched) {
if (matched) {
res.cookie("token", token, { expires: new Date(Date.now() + (5 * 60000)) , httpOnly: true }).json({ "message": "You logged in sucessfully!" });
} else {
res.status(500).json({ "message": "Wrong password" });
}
});
} else {
res.status(500).json({ "message": "Wrong email" });
}
} catch (error) {
console.log(error.message);
}
}
Here is the react.js code that I am using to fetch data from api without using a proxy in package.json file
if (errors.length === 0) {
const isLogin = await fetch("http://localhost:5000/api/login", {
method: "POST",
body: JSON.stringify({ email, password }),
headers: {
"Content-Type": "application/json"
}
});
const res = await isLogin.json();
if(res) alert(res.message);
}
I want to get to know what is the reason behind this "getting cookie in postman but not in the browser". Do I need to use any react package?
The network tab screenshot might help you.
If I see in the network tab I get the same cookie, set among the other headers
To my understanding, fetch doesn't send requests with the cookies your browser has stored for that domain, and similarly, it doesn't store any cookies it receives in the response. This seems to be the expected behaviour of fetch.
To override this, try setting the credentials option when making the request, like so:
fetch(url, {
// ...
credentials: 'include'
})
or, alternatively:
fetch(url, {
// ...
credentials: 'same-origin'
})
You can read more about the differences between the two here.
I got my error resolved with two changings in my code
In front end just added credentials: 'include'
fetch(url, {
method : "POST"
body : body,
headers : headers,
credentials: 'include'
})
And in back end just replaced app.use(cors()); to
app.use(cors({ origin: 'http://localhost:3000', credentials: true, exposedHeaders: ['Set-Cookie', 'Date', 'ETag'] }))
That's it got resolved, Now I have cookies stored in my browser!!! Great. Thanks to this article:
https://www.anycodings.com/2022/01/react-app-express-server-set-cookie-not.html
during development i also faced same things, let me help you that how i solve it,
Firstly you use proxy in your react package.json, below private one:-
"private": true,
"proxy":"http://127.0.0.1:5000",
mention the same port on which your node server is running
Like:-
app.listen(5000,'127.0.0.1',()=>{
console.log('Server is Running');
});
above both must be on same , now react will run on port 3000 as usual but now we will create proxy to react So, react and node ports get connected on same with the help of proxy indirectly.
Now, when you will make GET or POST request from react then don't provide full URL, only provide the path on which you wants to get hit in backend and get response,
Example:-
React side on sending request, follow like this:-
const submitHandler=()=>{
axios.post('/api/loginuser',
{mobile:inputField.mobile,password:inputField.password})
.then((res)=>{
console.log(res);
})
.catch((err)=>{
console.log(err);
})
}
Node side where it will hit:-
app.post('/api/loginuser', async(req,res)=>{
//Your Code Stuff Here
res.send()
}
on both side same link should hit, it is very important
it will 100%.
don't forget to mention
on node main main where server is listening
I'm using the npm module csurf for generating a token. First I get the token from the server, which I then use for the /register request. When I'm reproducing the same steps with postman, it seems to work, but unfortunately not in the application. There it always throws the error message that the token is invalid
--- Server side ---
csrfProtection.js
import csrf from 'csurf';
export default csrf({
cookie: true
});
router.js
import csrfProtection from './../../config/csrfProtection'
router.get('/csrfToken', csrfProtection, async (req, res, next) => {
return res.send(req.csrfToken());
});
router.post(
'/register',
validationHelper({ validation: registerUserValidation }),
csrfProtection,
async (req, res, next) => {
return res.send('user registered');
}
);
app.js
const app = express();
app.use(cookieParser());
app.use(
cors()
);
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
--- Client side ---
const token = await this.$axios.$get('/csrfToken')
// token has the value 1KFmMJVz-dspX9TJo8oRInPzgDA1VY28uqQw
await this.$axios.$post(
'/register',
{
name: 'test456',
email: 'test#gmail.com',
password: '123456789'
},
{
headers: {
'csrf-token': token
}
}
)
Someone experienced the same problem? Frontend and backend are hosted on different domains.
Recently fixed a similar issue regarding 403 for csrf token. A new CSRF token is generated for every post request that happens after the get csrf call.
I found that this is a CORS issue. I fixed by adding below code:
import cors from 'cors';
const allowedOrigins = ['http://localhost:3000', 'http://localhost'];
const corsoptions: cors.CorsOptions = {
allowedHeaders: ["Origin", "X-Requested-With", "Cookie", "Content-Type", "Accept", "X-Access-Token", "Authorization"],
credentials: true,
methods: "GET,PATCH,POST,DELETE",
origin: function (origin, callback) {
// allow requests with no origin
// (like mobile apps or curl requests)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) === -1) {
var msg = 'The CORS policy for this site does not ' +
'allow access from the specified Origin.';
return callback(new Error(msg), false);
}
return callback(null, true);
},
preflightContinue: false,
};
export const handleCors = (router: Router) =>
router.use(cors(corsoptions));
Please refer to cors package https://www.npmjs.com/package/cors"
You need to add it in your app.js below the cookieParser like so:
app.use(cookieParser())
app.use(csrfProtection)
You are successfully sending a CSRF token to the frontend in your /csrfToken but then your app is generating a new token in your /post.
Here is the link to the respective documentation.
My code:
const model = require('../db/models/user');
const describe = require('mocha').describe;
const assert = require('chai').assert;
const chaiHttp = require('chai-http');
let chai = require('chai');
let server = require('../server');
chai.use(chaiHttp);
describe('Test user registration, login, update password', () => {
beforeEach((done) => {
// Reset user mode before each test
model.User.remove({}, (err) => {
console.log(err);
done();
})
});
Now, I get the error
UnhandledPromiseRejectionWarning: TypeError: Cannot read property
'_id' of null
in the route itself, specifically:
router.put('/me/update-password', async (req, res, next) => {
const {body} = req;
const auth = req;
const userId = auth._id; // problem on this line!
// rest of code...
});
So, after registration and logging in (which works fine, as it should!), I am having a lot of problems to update the password. In the params I am sending generated token and in the body is the password field with new password. On live example (for example Postman) it works as it should, but in tests it simply does not.
I really have no idea and have lost a lot of my time over this already (3 days).
Can someone please take a look suggest solution?
Much appreciated.
Updated with auth.js:
const jwt = require('jsonwebtoken');
const isAu = function(req) {
return jwt.verify(req.headers.authorization.split(' ')[1], 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
module.exports = isAu;
EDIT:
Since OP changed the original question after it has been answered here is the link to original: https://stackoverflow.com/revisions/55064109/1
=======================================
JWT verify method accepts Authorization token - you are fetching that correctly by splitting Authorization header string in order to fetch token.
HTTP Authorization header string hold Authentication scheme type (Bearer, Basic, Digest, etc) and the token value
Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9...TJVA95OrM7E20RMHrHDcEfxjoYZgeFONFh7HgQ
but your Authorization header in the Chai request only holds the value of the token and not the Authentication scheme type.
Assumin your Authentication scheme is Bearer you need to set that in your Chai request Authorization header:
...
chai.request(server)
.put('/api/me/update-password')
.set('Authorization', `Bearer ${token}`)
.send(`${updatedPassword}`)
.end((error, response) => {
assert.equal(response.status, 200);
done();
});
...
On the other hand, in case you do not specify Authentication type in the request authorization header than you should send it like that to JWT to veirfy:
const isAuthenticated = function(req) {
return jwt.verify(req.headers.authorization, 'secret', function (err, decoded) {
if (err) {
return null;
}
return decoded;
});
};
I'm trying to build an API that receives a POST req to create a user but I am getting undefined errors for all of my req.body requests. My app is set up like this (simplified for brevity):
User controller that gets called by Express Router in my user routes file
/controllers/user.js
userController.addUser = function(req, res) {
let user = new User();
user.username = req.body.username;
user.first_name = req.body.first_name;
user.last_name = req.body.last_name;
user.email = req.body.email;
user.type = req.body.user_type
// This returns undefined as does all other req.body keys
console.log("REQ.BODY.EMAIL IS: " + req.body.email);
}
User Route File:
/routes/user.js - requires user controller above
router.post('/user/create', userController.addUser);
Main App:
all routes and controllers work per my tests except where req.body.* is used
index.js - main app file
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use('/api', routes);
I have looked through the Express documentation and through countless StackOverflow posts with no luck. Let me know if you need further clarification.
My issue was how I was sending the body to the API endpoint. I was using form-data instead of x-www-form-urlencoded with Postman. User error
Sometime with change in version body-parser seems to not work, in that case just use following, this will remove dependency from body-parser:
router.post('/user/create', (req, res, next) => {
let body = [];
req.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
// Data is present in chunks without body-parser
body.push(chunk);
}).on('end', () => {
// Finally concat complete body and will get your input
body = Buffer.concat(body).toString();
console.log(body);
// Set body in req so next function can use
// body-parser is also doing something similar
req.body = body;
next();
});
}, userController.addUser);
I am trying to debug a failing JWT auth setup, which always returns a 401.
My passport setup (middleware/auth.js)
import passport from 'passport'
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
module.exports = function() {
var options = {};
options.jwtFromRequest = ExtractJwt.fromAuthHeader()
options.secretOrKey = 'superdupersecret'
var strategy = new JwtStrategy(options, function(payload, done) {
console.log('this is not printing') <---------------
var user = payload.sub || null;
if (user) {
return done(null, { id: user._id });
} else {
return done(new Error("User not found"), null);
}
});
passport.use(strategy);
return {
initialize: () => {
console.log('this only prints on boot'); <---------------
return passport.initialize();
},
authenticate: () => {
console.log('this too') <---------------
return passport.authenticate("jwt", {session: false});
}
};
};
My server.js file where I initialize passport:
import express from 'express'
(...)
var auth = require("./middleware/auth.js")();
// Instantiate app
const app = express();
// Initialize passport for auth use
app.use(auth.initialize())
And my protected route that always returns a 401:
import express from 'express'
var auth = require("../middleware/auth.js")();
const userRouter = express.Router()
userRouter.get('/dashboard', auth.authenticate(), (req, res) => {
res.send('It worked! User id is: ' + req.user + '.')
})
export default userRouter
I have tried to add print statements within the actual passport.js module itself, as well as passport-jwt, with no success.
After the authentication middleware on the protected route, nothing logs.
I have tried a ton of setup permutations over the past 3 days now. Any help would be greatly appreciated
Ok, I followed the tutorial you mentioned and it seems to work.
Here are some notes (some may be obvious, no offense).
Copy exactly the code as the tutorial
After you have everything, you need to "login". Make a POST request to /token. Content type has to be application/json and on the body of the request you need to sent an object with email and password (from tutorial).
After you login, the server returns a token.
Take that token and now make a GET request to /user. In the headers of the request add: Authorization: JWT [your token here]. You have to write "JWT" and the token separated by one space.
The server returns a status 200. I modified so it returns the user.
app.get("/user", auth.authenticate(), function(req, res) {
res.json({user: req.user});
});