im trying to make a login endpoint/api, and i am sending the user login data through the authentication headers, is that okay for the security?
the authorization headers is actually contains an object, it is encoded to a Base64, it contains user data like hashed password (not hashed yet in this code), username, and some sort of a serverkey (to authorize if it is the right client that sending an api request), just wanna make sure if it is secure or not..
const aFunction = (req, res) => {
require('crypto').randomBytes(48, function(err, buffer) {
const token = buffer.toString('hex');
const auth = JSON.parse(Buffer.from(req.headers.authorization, 'base64').toString('ascii'))
if(auth.serverkey == version["serverkey"]){
loginUser(auth.username,token).then(data =>{
if (data.rows.length < 1 || data.rows[0].password != auth.password) {
res.send({
status: "failed",
message: "Username/Password is wrong",
status_code: 400
})
}else{
res.send({
status: "success",
message: "successfully logged in",
status_code: 200, token: token
})
}
})
}else{
res.send({
status: "failed",
message: "Unauthorized Client",
status_code: 401
})
}
});
}
I would add a check for if (req.secure) to your code, and reject any non-https request coming your way. (Don't do this if your nodejs program is behind a reverse proxy.)
Users with browsers can see everything you send back and forth to your server, whether via https or http. (devtools) So, if by reading your headers they can figure out how to send you malicious headers, then somebody will. And base64 encoding has exactly the same security as no encoding at all. So salt and hash your passwords. Use the bcrypt package or jwts.
I would handle errors this way, by calling next() with an error parameter rather than by using .send() to deliver a failure message. Use this sort of code.
const createError = require('http-errors')
...
const myfunction (req, res, next) {
...
if (!req.secure) return next(createError(403, 'https required'))
...
});
Related
I made a back-end in js node and mysql to be able to login and access to secure routes with jwt.
Until now i'm able to login and i get a token. I created a route protected by a middleware (like the code below) but when i try to get this route i always get a 401 Unauthorized and error 'Your session is not valid'. I follow this tutorial step by step but doesn't work for me. The complete code is in the link for details.
Thanks in advance :)
module.exports = {
isLoggedIn: (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(
token,
'SECRETKEY'
);
req.userData = decoded;
next();
} catch (err) {
return res.status(401).send({
msg: 'Your session is not valid!'
});
}
}
};
I see that you figure it out
my point is (or my problem) is with this line of code
req.headers.authorization.split(' ')[1]
that's wrong what's going to happen when I just sent you a token like
tokennnnnnnnnnn
so my fix is something like that (usually it's going to be 'Bearer')
so why not something like that
req.header.authorization.replace(/bearer/i, '')
something like that I think it's going to be nice! <3
i was wandering what is the best way to avoid sending the user data on every request,
lets say i want to add product from user's account, i have to send the user. or i want to order something, i have to send the user.
i thought about something like this:
app.use(async (req, res, next) => {
if (!req.body.userId) {
return next();
}
const user = await enteties.User.findByPk(req.body.userId);
req.user = user;
next();
});
but it also requires me to send the user on evey request..
there must be a way to avoid sending the user data to the server on almost every request.
also, it will make all my requests of type "post" since i have to send the user, and even "get" requests are now become "posts", for sure this is not correct
If you implement your JWT token correctly you don't need to send the logged in user id.
JWT tokens contain a payload section that is basically any JSON data you want to set. This is basically your decentralized session stored in the user's machine. When creating a JWT token you'd normally do something like:
const jwt = require('jsonwebtoken');
const config = require('./config');
function generateToken(user) {
let payload = {
sub: user.id
};
return jwt.sign(payload, config.secret, {
algorithm: 'HS512', // choose algorithm appropriate for you
expiresIn: config.expires
})
}
That payload part allows you to send user identifying information. In the case above, the user id. To get that id from a request simply verify it:
app.use((req, res, next) => {
const token = req.get('Authorization');
jwt.verify(token, config.secret, (err, payload) => {
if (err) {
next(err);
}
else {
req.user = payload; // user.sub is the user id
next();
}
});
});
Or you can use a library such as express-jwt to do it for you:
const expressJwt = require('express-jwt');
const express = require('express');
const config = require('./config');
const app = express();
app.use(expressJwt({ secret: config.secret }); // use express-jwt like any
// middleware, you can even install
// it on specific routes.
Now in your controller/route you can simply extract the payload in the req.user object. Invalid tokens or requests without tokens will completely skip your handler and immediately return an error or unauthorized response:
app.get('/some/endpoint', (req, res) => {
console.log('user is', req.user.sub); // note: req.user is our payload
});
Additional tricks:
As I mentioned, the payload is basically user defined. If you need to keep track of other user information such as roles or permissions you can store them in the JWT token:
// Example payload
let payload = {
sub: user.id,
admin: user.role === 'admin',
gender: user.gender
};
This reduces the number of database requests needed to process the user. Making the authentication system completely decentralized. For example you may have a service that consumes this JWT token that is not connected to your user database but need to check if user is admin. With the right payload that service does not even need to have access to the user database.
Note however that the payload is not encrypted. It is just base64 encoded. This means that the information in the token can be easily read by anyone with access to it (normally the user but beware of 3rd party scripts). So ideally you shouldn't store dox-able information in the payload if you have 3rd party scripts on your website (then again, it is highly unusual these days for anyone to write the entire front-end from scratch without any libraries or frameworks)
Also note that the more you put in your payload the larger your token will be.
I have a system that works perfectly using postman, I can POST to
localhost:1337/api/user/login with username and password set in the body of the request. This then verifies the login and allows me access to further calls. (And repeated calls to localhost:1337/api/user/login recognize I'm already logged it and respond to that).
When I check the server I notice that postman is indeed sending the (htmlonly) cookie with each request I make.
Now I tried to do the same log in through sencha 6.5 modern. In some controller I have the following code:
const form = this.getView();
if (form.isValid()) {
const data = form.getValues();
const u = 'http://localhost:1337/api/user/login'; //form.getUrl();
debugger;
Ext.Ajax.request({
url: u,
method: 'POST',
params : data,
success: function(response, opts) {
const obj = Ext.decode(response.responseText);
console.dir(obj);
},
failure: function(response, opts) {
console.log('server-side failure with status code ' + response.status);
}
});
}
This makes the "correct" call to the database, and I am seeing the credentials as expected. However I notice that on any future calls the session cookie is not being send stored (chrome doesn't store a cookie), and each time a new session cookie is being send/created.
What creates this discrepancy? Why does sencha/chrome not store the cookie?
For completeness sake, the server in sails (which uses expressjs session storage) goes to:
login: async function(req, res) {
try {
const user = await User.findOne({username: req.body.username});
const sess = req.session;
if (sess.userId === user.id) {
res.json({succes: false, message: 'already logged in'});
return;
}
if (!user) {
res.json({success: false, message: 'Authentication failed. User not found.'});
} else {
// check if password matches
const valid = await comparePromise(req.body.password, user.password);
if(valid) {
// if user is found and password is right
req.session.userId = user.id;
res.json({success: true, user: user});
} else {
res.forbidden(JSON.stringify({ success: false, message: 'Authentication failed. Wrong password.' }));
}
}
} catch (err) {
console.log(err);
res.badRequest(JSON.stringify({success: false, message: err.message}));
}
},
If your webpage is at one port and your ajax call is made to another port, this could explain why cookies are not being saved (even if the rest of the request and response works).
I think that different ports are treated as different origins. This would certainly explain why postman is successful while the code run from the page is not. I can tell you how I solved this problem (not for ports, but for different domain names served by the same ip) in my app...
In the ajax call, you need to set it up to allow credentials since the url is different origin:
$.ajax({
url: 'http://somehost.com/some/path',
method: 'GET',
xhrFields: { withCredentials: true },
success: function(data) {
// the cookie should be set here
}
});
Then you have to allow this at the receiving url. This can be insecure. In my case, I allowed cross origin requests at a very specific url only by configuring the routes.js file:
'GET /some/path': {
controller: 'MyController',
action: 'someAction',
cors: {
origin: '*',
credentials: true
}
},
In my case, that allowed any origin to access that route with credentials, and get a cookie. In your case, you might be able to use something like
cors: {
allowOrigins: ['http://localhost:1337', 'http://localhost:1841']
credentials: true
}
... but I'm not sure I have the syntax just right for that. Some docs are here.
If you need to do more universal setup over your whole app, I think sails has some ways to do that from the cors.js config file, but you should think through the security before doing this.
I think that once you have the cookie, it should be included with all requests across all ports (ie, I don't think including cookies is port specific, only storing them).
Good luck!
I am currently working on registration module. I am able to register using a form. I am using Angular JS for frontend and Nodejs for backend services. I call the API and thus it gets registered. After registering, the user gets a verification mail upon which clicking the button, the user should be successfully verified.
I have achieved till sending a mail but after clicking on 'verify button' in mail it lands onto login page directly without verifying. I want to verify the user and send the token to the verify API. I am not sure about how to go about it.
Use node jwt module which solve your problem.
When user login then create token for user
var token = jwt.sign(user, app.get('superSecret'), {
expiresIn: '1440m' // expires in 24 hours
});
Then sent token with response each time when user try to access any url create middle-ware that will verify user token it is valid or not.
/* Middle-ware to verify toke */
apiRoutes.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x- access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ status: 102, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
I have two applications, both on Nodejs. One front-end and other back-end.
My back-end app is protected with token access using express-jwt and jsonwebtoken middlewares.
My problem is: I am making a request from front-end to back-end passing the token on header, back-end accepts the request and respond properly. Then in the front-end I redirect the response to an specific page (res.redirect('/')), in that moment I get the error UnauthorizedError: No authorization token was found
My front-end request:
/* Authentication */
router.post('/', function(req, res, next) {
// request login service
request({
uri: env.getUrl() + "/user",
method: 'POST',
timeout: 10000,
headers: {
'Authorization': 'Bearer '.concat(global.token)
},
form: { login : req.body.login, pwd : req.body.pwd }
}, function(error, response, body){
if(error) {
logger.error(error);
res.render("error", {message: "Error getting user" });
}
else {
if(body){
req.session.usuario = JSON.parse(body);
res.redirect("/");
} else {
res.render("login", {message: "Login Failed" });
}
}
});
});
I don't know why this happen. Could you help me?
Thanks in advance.
A redirect (via res.redirect) issues a new HTTP request. This means that the Authorization header is empty. This results in the UnauthorizedError error.
To fix this, you have two options:
1. Pass the token in the URI
You can issue the redirect with the token passed in the URL in this way:
res.redirect("/?access_token=" + global.token);
2. Set the header before the redirect
You can set the 'Authorization' header before making the redirect request:
req.session.access_token = global.token;
Problem found.
Anytime the my front-end app makes a request to the back-end side (api) the user logged in front-end is validated against back-end and so the fron-end's session is updated as well. Which means that every request is actually two requests:
One as the real request the app is doing.
The request validating the user logged on front-end in order to be sure that user exists.
This update (second point) was made without providing a token.