Am trying to wrap my head around passport.js, and am sure I am not using it correctly.
Particularly with the serializeUser() and deserializeUser() functions. My functions look similar to the two below. I am essentially pushing my database user._id into the session (which I don't like to do), and can view it easily in the browser with a cookie viewer.
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
done(null, id);
});
So, for every secure request, I run an isAuthenticated() function which checks that the user is Authenticated with Passport, and then I use the deserialized ID in req.user to perform various CRUD operations on the database.
What I am wondering is what would stop an authenticated user from replacing their own user._id with someone else's user._id in the session, and gaining access to all these CRUD db operations for a different user.
Does passport have some sort of hashing algorithm to check the serialized data hasn't been tampered with?
Should I not be using the returned ID for my database CRUD operations?
Is there another way to avoid sending the user._id to the browser?
This is the session middleware which makes sure that session data is not tampered, you just need to provide the secret option which will be used to sign it.
From express session middleware documentation (https://github.com/expressjs/session)
secret - session cookie is signed with this secret to prevent tampering.
Ex. app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
According to the passport docs(http://passportjs.org/guide/configure/):
The serialization and deserialization logic is supplied by the
application, allowing the application to choose an appropriate
database and/or object mapper, without imposition by the
authentication layer.
So, you have to implement serializeUser() and deserializeUser() methods. Serialize method will return the identity information to recover the user on future requests. And that identity information will be saved in the session. Deserialize method should be able to identify the user based on that identity information. Whether you use database id, some randomly generated string, UUID or something similar is up to you and your security requirements.
Related
I'm a little confused about the flow of data in a React application that authorizes a user through a third party OAuth provider. Right now I have a login button in React that directs to a Node server.
Log In
This directs to an Express route...
router.get('/auth/google',
passport.authenticate('google', {
session: false,
scope: ['email']
}));
that uses Passport to verify the login request.
passport.use(new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: "http://localhost:5000/api/auth/google/callback"
},
function (accessToken, refreshToken, profile, cb) {
return cb(null, profile);
}
));
The callback is set up to redirect the user back to the React application.
router.get('/auth/google/callback',
passport.authenticate('google', {
session: false,
failureRedirect: 'http://localhost:3000'
}),
function (req, res) {
// Successful authentication, redirect home.
res.redirect('http://localhost:3000');
});
This method allows me to either add a user to my DB via my Node app, or confirm the existence of the user already in my DB, and that's as far as I've seen any tutorial take it, but it seems entirely useless without the ability to send this information back to React. The point of logging in, at least in my opinion, is to have my application function specific to a user.
What I'd like to be able to do is send back a signed JWT token. Either one signed manually with my server, or the access token that passport gets from Google, either will do. As well, I'd like to send a user ID. I need this so my app can make API calls with the user ID as part of the request to protected routes, thus the need for the JWT.
It's confusing to me because without this exchange of data, the purpose of OAuth seems essentially useless, so I must be missing a vital step in the process, or I must be looking at the problem from the wrong perspective. Where or how should I be informing my React application of the details of the logged in user, and give them a token for local storage? As far as I can tell, there's no way to send package of data back with the callback URL.
I found a way, you can use eventsource for this. Check this article for more information https://www.blog.tericcabrel.com/implement-server-sent-event-in-node-js/
You can send you token as query param in your frontend app
res.redirect(`http://YOUR_FRONTEND_HOST:3000/?token=` + "Your jwt token")
So in the frontend you can retrieve the token with a useEffect then make it disappear to don't make it avalible to the user.
I'm currently trying to build a REST API using express, node, and MongoDB. Now for authentication, I'm using JWT.
Here is the code for checking JWT token
const token = req.headers['authorization'];
if (token){
const tokens = token.split(' ');
const key = tokens[1];
jwt.verify(key, config.jwtKey, (err, authData) => {
if (err){
res.status(403).json({
success: false,
message: "Authentication2 failed"
});
}
// User authenticated
// Do something
next();
});
} else {
res.status(403).json({
success: false,
message: "Authentication failed"
});
}
Now, this code is working perfectly.
For making the JWT, here is the code
........
........
const token = jwt.sign(
{
email: user[0]._email,
userId: user[0]._id
}, config.jwtKey,{ expiresIn: "1d" });
........
........
Now my question is should I also verify the user by checking the existence of the user's information in the database to make it more secure?
For example, searching the email and userId to my database.
I'm using node, express, MongoDB, Mongoose, and JWT for this project.
It's an old question but I want to leave an answer:
Yes! Everytime your client make an api request, backend should verify both the validity of the token and the presence somewhere in your backend (for example a db table).
Consider always the most dangerous scenario: bank account
What happens if someone steal your device?
You should be able to invalidate the token from another device and change the password.
The advantage of using a token is that the server can
verify it quickly without calling out to an external data store like MongoDB.
But if you're going to add a business login to your API authentication like a blacklist/whitelist of revoked tokens then you have to use a store to verify the token and user details, (will be slower than not doing a remote call for each token but you have to do it with low latency).
For low latency you have to use DB like Redis, Dynamodb would probably be fine and more secure without major latency between your DB and your API server.
Is not required to verify the signature with DB, and you can settle for JWT algorithm
Verifying (the signature of) the token using the selected algorithm is enough to ensure that this user exists (or existed) on the system because it was the system who generated the token in the first place.
But there are cases when that's not enough, for example, blacklists as mentioned in Roy G's answer, or if the users' claims have been changed or completely deleted from the system but they are still using an old token (not expired yet), they could still have access to the system, so checking against DB would prevent that access.
Setting a small expiry date in combination with refresh tokens is generally a good practice to prevent those kinds of leaks.
I have a MEAN stack app with a REST like api. I have two user types: user and admin. To sign the user in and keep the session i use jsonwebtoken jwt like this (simplified):
const jwt = require("jsonwebtoken");
//example user, normally compare pass, find user in db and return user
let user = { username: user.username, userType: user.userType };
const token = jwt.sign({ data: user }, secret, {
expiresIn: 604800 // 1 week
});
To protect my express routes from I do this:
in this example it is a "get user" route, the admin is ofc allowed to get information about any given user. The "normal" user is only allowed to get information about him/her -self, why i compare the requested username to the username decoded form the token.
let decodeToken = function (token) {
let decoded;
try {
decoded = jwt.verify(token, secret);
} catch (e) {
console.log(e);
}
return decoded;
}
// Get one user - admin full access, user self-access
router.get('/getUser/:username', (req, res, next) => {
let username = req.params.username;
if (req.headers.authorization) {
let token = req.headers.authorization.replace(/^Bearer\s/, '');
decoded = decodeToken(token);
if (decoded.data.userType == 'admin') {
//do something admin only
} else if (decoded.data.username == username) {
//do something user (self) only
} else{
res.json({ success: false, msg: 'not authorized' });
}
} else {
res.json({ success: false, msg: 'You are not logged in.' });
}
})
So my question is, how secure is this? Could someone manipulate the session token to swap the username to someone else's username? or even change the userType from user to admin?
My guess is. Only if they know the "secret" but is that enough security? the secret is after all just like a plain text password stored in the code. What is best practice?
the secret is after all just like a plain text password stored in the code.
That's right. If secret is not kept secret, then an attacker can forge user objects and sign them using that secret and verify would not be able to tell the difference.
Putting secrets in code risks the secret leaking. It could leak via your code repository, because a misconfigured server serves JS source files as static files, or because an attacker finds a way to exploit a child_process call to echo source files on the server. Your users' security shouldn't depend on noone making these kinds of common mistakes.
What is best practice?
Use a key management system (KMS) instead of rolling your own or embedding secrets in code. Wikipedia says
A key management system (KMS), also known as a cryptographic key management system (CKMS), is an integrated approach for generating, distributing and managing cryptographic keys for devices and applications.
Often, how you do this depends on your hosting. For example, both Google Cloud and AWS provide key management services.
https://www.npmjs.com/browse/keyword/kms might help you find something suitable to your stack.
This is extremely secure, especially if sent over HTTPS - then an attacker has no idea what your request payload looks like.
The only real danger is making sure you keep the SECRET safe, don't save on public git repos, lockdown access to any box on which the secret is stored. Use an encoded secret.
There are also other ways to harden your server. Consider using npm's popular helmet module.
This is secure if your data packets are sent over HTTPS. If you want to add another layer of security what you can do is you can first encrypt the user details with the help of iron which uses 'aes-256-cbc' for encryption and then use that encrypted text and generate a token through JWT. In this way if a user somehow gain access to your token and go the website of JWT. He won't be able to recognize what's inside this token.
Again this is just to add an extra layer of security and it makes technically infeasible for a person to extract information because it's very time consuming yet add some extra security to our application.
Also make sure that all of your secrets(secret keys) are private.
To answer your initial question "Could someone manipulate the session token to swap the username to someone else's username? or even change the userType from user to admin?"
JWT tokens are encrypted from the server-side and sent to the client in the form of a response. With that token, there are two things to keep in mind:
Token is encrypted with a private key that only the server knows about
The token contains, what is known as, a signature which validates the contents of the JWT payload (e.g. userType etc.)
For more information regarding the first point, please refer to the following answer.
For a better visual representation of JWT tokens and signatures, take a look at the following URL - https://jwt.io/
With this in mind, you must make sure that:
Your secret key is never exposed at any point in time to external services/clients. Ideally this key is not even hard-coded in your codebase (ask, should you want me to elaborate on this).
You don't have any logical flaws in your endpoints.
From a design perspective, I would much rather segregate any endpoints related to admin-space into their own endpoints however at a quick glance, your code seems to be fine. :-)
On a side-note that may assist you, if you don't have much experience in Web Application security, I would recommend checking out some automated scanners such as Acunetix, Burp (hybrid) and so on. Whilst not perfect by any means, they are quite capable of detecting a good amount of behavioral vulnerabilities (as in, ones a malicious actor would normally exploit).
Some potential vulnerabilities in the code above:
Variable username comes from the url, but usertype is asserted from the token in the router action. An audit log for example could be corrupted if it took the userid from the username variable, because that is not authentic and can be anything sent by the user.
Replay is an issue. As the token is valid for one week, it is impossible to revoke admin rights for example until the token expires. (A malicious user could replay the previous admin token.)
Similarly, you cannot terminate (force logout) any session until tokens expire. This is a common issue in such stateless designs.
Null reference after token decode as pointed out by others. In node.js that is more like a weakness than a vulnerability I'd say, I think it's not exploitable.
The secret on the server is extremely valuable and can be used to impersonate any user. It is very hard to protect such a secret in systems with high security requirements.
So contrary to another answer, this is far from being 'extremely secure', but can be reasonably secure for many purposes.
I have a SailsJS website for which I implemented authentication through a form where user needs to fill in email and password. copied from ActivityOverloard 2.0 example code
Login
login: function(req, res) {
console.log("Login hehe!!");
// Try to look up user using the provided email address
User.findOne({
email: req.param('email')
}, function foundUser(err, user) {
if (err) return res.negotiate(err);
if (!user) return res.notFound();
console.log("found email");
// Compare password attempt from the form params to the encrypted password
// from the database (`user.password`)
require('machinepack-passwords').checkPassword({
passwordAttempt: req.param('password'),
encryptedPassword: user.encryptedPassword
}).exec({
error: function(err) {
console.log("There was an error with password");
return res.negotiate(err);
},
// If the password from the form params doesn't checkout w/ the encrypted
// password from the database...
incorrect: function() {
console.log("Password doesn't checkout w/ the encrypted");
return res.notFound();
},
success: function() {
console.log("Good password");
var now = new Date();
User.update(user.id, { online: true, lastLoggedIn: now }, function() {
// Store user id in the user session
req.session.me = user.id;
User.publishUpdate(user.id, {
online: true,
id: user.id,
name: user.name,
lastLoggedIn: now,
action: ' has logged in.'
});
// All done- let the client know that everything worked.
return res.ok();
});
}
});
});
my page is protected with login
myPage: function(req, res) {
if (!req.session.me) {
return res.view('login'); // not authenticated will take you to the login page
}
// It's authenticated, it runs the code below
// DO SOMETHING
Now a very special use case, I need to open my page without user interaction (It can't be through a form) but I still need it to be protected. I'd need to pass some kind of access token.
I understand that passing an "access token" as query param is most probably not a good idea isn't it?
In fact, I don't know how to resolve my problem and allow to access myPage other than a session based authentication through a user interaction via a form ...
It seems to me that I'd need to first get a token programmatically and then open a browse to my page ... I bet there is some best practices to address my problem out there.
Any pointers? may be someone can fill the knowledge gap.
Realisticly speaking, you have multiple options with regards to passwordless or formless logins in node.js/express.js and therefore sails.js, as sails is built on top of both.
How you would approach the solution, really depends on the scale and use of your application/applications. For example; will the same login credentials be used to access multiple applications or a single web application, will the application be available only in an intranet or across the whole WWW.
Regardless of the scenario above, there will next to always be some initial setup required by the user, whether that is an initial sign up with an identity provider or an initial sign up with your application. The sign up form, will not dissappear entirely, rather it will become a one time event.
So let's look at some options and how we might introduce them into an express/sails application/s, I will start with the most basic and work down in difficulty.
Option 1:
Make use of the sails session store. From your code, you have already started doing this. The logic works something like this:
Your user signs up or logs in for the first time. At this stage you set the users session to be authenticated.
// Store user id in the user session
req.session.me = user.id;
req.session.authenticated;
You set a policy on all the pages which require authentication. Luckily, sails has already done some of the heavy lifting here by creating a sessionAuth policy in the folder api/policies. To make use of them open the config/policies.js file and add this policy to your protected pages
'my_app' : {
'route_to_protect' : 'sessionAuth'
},
Finally, you will want to make this session cookie last a really long time, to do this open config/session.js and edit the cookie maxAge to suit your needs. For example, if you want to force the user to login every 365 days, you might do some like this:
// milliseconds * seconds * minutes * hours * days
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365
},
The draw back to this option is that your sessions will be lost if the application is restarted and all users will have to log in again.
Option 2:
Use a simple third party library like passwordless. Passwordless offers token-based authentication for express web applications and as sails is built on top of express...
The general jist of passwordless is when a user signs up, you deliver them a link to your application via email, this will log them in and in turn set up there session. Passwordless makes use of mongo as a session store, so you can either install mongo or use something like mLab which is a Mongo Database-as-a-Service provider. For a complete run through on using passwordless, take a look at their getting start page here.
Now for the more featureful based options.
Option 3:
If you are developing an application that is public facing, making use of Passport.js with sails is a great option.
Passport is authentication middleware for Node.js. Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.
Passport works with Sails just like it does with Express.
There are already a shed load of guides on setting up passport out in the ether. But a great step-by-step is available here and is also the one referenced by sails in there official documentation here.
Passport is in all essence an authentication middleware. It allows users to identify themselves based on this authentication, you can develop the correct authorization functionality in your application.
Option 4:
Make use of SAML or OAuth. From a development and implementation perspective, these are by far the biggest undertaking out of the options provided.
SAML and OAuth are authorization middleware which refers to rules that determine who is allowed to do what. Both have a very similar setup and make use of an Identity Provider(IdP) and Service Provider(SP), where the IdP represents an online service that authenticates users in the flow and the SP represents an application that relies on a trusted IdP for authentication and authorization.
I am more familiar with SAML, so what follows is with reference to considerations when implementing SAML in a project.
You will first need to register your application (SP) with an IdP. With regards to IdP's, what you choose is based on the scale and requirements of your application, there are free online IdP's like ZXIDP and SSOCircle or if your application required a dedicated IdP you could look at something like OpenSSO. You could also consider creating your own Node.js IdP using the saml-idp package.
Integrating SAML into a sails application is not overly difficult. Make use of the saml2-js package.
Once all configured, the logic works something like this.
User opens their web-browser and goes to yoururl.
To authenticate the user yoururl constructs a SAML Authnrequest, signs, encrypts and encodes it.
Then yoururl redirects the user's to the IdP to authenticate.
The IdP validates the request, in the first signup/login, the IdP will ask the user to enter their username and password, after that it will use the sessioning and other than the address change in the browser address bar the user will not see much.
If the user is successfully authenticated, the IdP generates a SAML token that includes information about the user (username, etc) and redirects them with this token back to yoururl.
Finally yoururl verifies the SAML token, extracts the identity information about the user including authorisations and logs them in.
We have two types of Users, Admin and general Users.
passport.serializeUser(function(user, done) {
console.log('Sear');
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log(id);
console.log("Deser");
User.findById(id, function(err, user) {
if(err) done(err);
if(user){
done(null, user);
}else{
vendorUser.findById(id, function(err, user){
if(err) done(err);
done(null,user);
});
}
});
});
Console.log gets outputted frequently (even on a single API request) with the text
Deser
What do both the functions exactly do? A detailed answer is appreciated.
TIA.
Serialization and deserialization are important concept. To serialize an object means to convert its state to a byte stream so way that the byte stream can be reverted back into a copy of the object.
In a typical web application, the credentials used to authenticate a user will only be transmitted during the login request. If authentication succeeds, a session will be established and maintained via a cookie set in the user's browser.
Each subsequent request will not contain credentials, but rather the unique cookie that identifies the session. In order to support login sessions, Passport will serialize and deserialize user instances to and from the session.
In the code that you have written, only the user ID is serialized to the session. When subsequent requests are received, this ID is used to find the user, which will be restored to req.user.
In order to give developers freedom to user whichever database they want, whatever data they want to serialize, they can do it in their own way, the serialization and deserialization logic is left to us to implement.
serializeUser is the method that is called on the login request(during the authentication) and if login is successful then it decides what user information should get stored in the session and a cookie is sent to the browser for the same to maintain the session.
// Only during the authentication to specify what user information should be stored in the session.
passport.serializeUser(function (user, done) {
console.log("Serializer : ", user)
done(null, user.id);
});
The above snippet will save the user.id field to the session and cookie.
deserializeUser is the method that is called on all subsequent request and is called by the passport.session middleware. It enables us to load additional user information on every request. This user object is attached to the request as req.user making it accessible in our request handling.
Here is the article that explains it flow very well