passport-local cookie and session database handling - node.js

I am currently trying to setup an oauth service for my current project. Being new to all this, I have read many articles on the subject, but I am still missing some pieces.
My environment is:
servers: node, express, passport
authentication: local strategy (using my own accounts / passwords)
I have a database with user/password ( passwords run through bcrypt)
web interface: react, server accesses through superagent
On my nodejs server, I am using these modules:
express
express-session
express-mysql-session
passport
passport-local
bcrypt
Different parts of the solutions are working: I can create new users, create new sessions, see their content in the express-mysql-session database.
However I am quite confused on the following:
when my web client tries to access protected routes, I don't seem to be getting any cookie in the request. Is it that superagent will not send my passport cookie by default? I read somewhere that in single page apps, jwt might be more appropriate, is that linked to this problem?
despite all I read, I am still confused about deserializeUser. My understanding is that with the passport-local solution, upon access, the web client will send the session cookie, which contains the session Id. Passport will fetch further information concerning this session from database, and then continue to handle the request. Does this session info retrieval happen automatically (in express-mysql-session?)? Or do you have to "manually" do it in deserializeUser (many examples show a User.findById call in there)? If you have to do it "manually", it means that you have to access the express-mysql-session db using another connection than the one this module is using?
to log out, is req.logout() enough to ensure the session is erased from the session db entirely?

Answers I found so far:
One has to add the withCredential method to superagent, to get it to send authentication cookies:
res = await superagent
.get(url)
.withCredentials()
.send();
On the CORS side of things, on the server, the 'credentials' option is required if using the 'cors' npm module, for instance:
app.use(cors({
origin: ['http://localhost:3003'],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
}));
All session information is automatically retrieved by these modules. However, many example show this call going back to the user database to get more information (rights, other info). The goal is to avoid having the same information in two locations (sessions db, and user profiles db), and having these getting out of sync (when an account gets closed etc...)
req.logout() disconnects the session, but the session information sticks around in the database.
The following question put me on the right track: how to delete cookie on logout in express + passport js?. You need to use req.logout, res.session.destroy, and while you're at it res.clearCookie to delete the client cookie:
router.post('/logout/',
(req, res) => {
req.logout();
res.status(200).clearCookie('connect.sid', {
path: '/',
secure: false,
httpOnly: false,
domain: 'place.your.domain.name.here.com',
sameSite: true,
}).end();
req.session.destroy();
},
Session is disconnected, database cleaned, cookie gone.

Related

How to get HTTP-only cookie in React?

I'm currently developing a MERN stack application and the authentication I use is JWT and saving it in my cookie. This is how I send the cookie after the user login.
res
.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "none",
})
.send();
And I am logging in the user by getting the "token" cookie in my backend. However, I implemented Redux with this application and every time I refresh the page, it automatically logs out. What I want is to detect in my front-end(React) the "token" cookie in my browser and I can't get it. I've tried using npm js-cookie and still can't get it. Is there a way to get the "token" cookie? Or use redux-persist based on what I've read? Please help, thanks.
Like already explained by an other answer, you can't access httpOnly cookies via JS.
I personally would recommend you to use a diffrent approach. Sure, cookies and httpOnly sounds like a good Idea, and you may think that cookies are a thousand times better than localStorage, but at the end, it doesn't really matter if you store the token in localStorage or in a cookie. You could argue about cookies vs localStorage for hours, but both have their vulnerabilities (e.g.: cookies: CSRF-Attacks (https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html), localStorage: XSS).
Now, while you could theoretically use localStorage here, I am not advocating using it. I would recommand you to just ditch both cookies and localStorage and store the JWT in your app-state (be it with the context-api, redux etc.) and send the JWT with an authentication header with all the request you make from the front to backend. Of course your backend would then need to verify that token. You could, for example, just implement an authentication middleware that you add to all the routes that need authentication. Expiration is also really easy because you don't have to sync the expiration of the JWT and the cookie anymore. Just set the expiration on the JWT and the verification of that token in the auth middleware will catch that. If you want to know why this method is safe against CSRF-attacks, look here: Where to store JWT in browser? How to protect against CSRF?
Here are some good articles, I would really recommand you read a bit of the first one:
https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/
https://medium.com/#ryanchenkie_40935/react-authentication-how-to-store-jwt-in-a-cookie-346519310e81
Although you cannot do anything with the httpOnly cookie in the frontend, there definitely IS a way to handle frontend-sent httpOnly cookies and extract your JWT from that cookie, all in the backend of your MERN stack app.
As far as persisting the user and preventing the 'logout upon refresh' issue, you will have to create a useEffect hook that constantly checks whether the token exists or not - we'll get to that later.
First, I recommend using Cors in your backend:
const cors = require("cors");
app.use(
cors({
origin: ["http://...firstOrigin...", ...],
credentials: true,
})
);
Once that's ready to go, set the following options when creating your httpOnly cookie. Also, create a non-httpOnly cookie that tracks your httpOnly cookie with same expiration date and a boolean value instead if the JWT. This will allow you to use the 'universal-cookie' library and actually read the non-httpOnly cookie in the frontend:
res
.cookie("token", token, {
origin: "http://...firstOrigin..."
expires: // set desired expiration here
httpOnly: true,
secure: true,
sameSite: "none",
})
.cookie("checkToken", true, {
origin: "http://...firstOrigin..."
expires: // same as above
secure: true,
sameSite: "none",
})
Having created a 'checkToken' cookie that mimics our actual 'token', we can use it to set the state (useState hook) and persist the user if it exists and not expired, through the useEffect hook.
However, to send it correctly, we must specify a few things first. In this example, I will use axios to make such API call in the frontend.
Note that every API call's request header will contain our httpOnly cookie and it's content - we can confirm this by opening chrome dev tools' network tab, make the API call, then check the "Request Headers" for the "Cookie"...
const cookies = new Cookies();
const checkToken = cookies.get("checkToken");
const AuthUser = () => {
const [user, setUser] = useState(checkToken);
useEffect(() => {
async function checkToken() {
await axios
.post("http://...yourBackend.../authToken", {
withCredentials: true, // IMPORTANT!!!
})
.then((res) => {
// handle response - if successful, set the state...
// to persist the user
)}
.catch((err) => {
// handle error
)}
};
checkToken();
}, []);
// Implement your login behavior here
}
Once that's done, we can confirm that we're getting such token in the request body of our API call in the backend (wherever that's handled), log the cookie in the console to check for it, then store the cookie's value in a variable to enable verification of said cookie:
app.post(".../authToken", (req, res) => {
// Get all cookies from request headers
const { cookie } = req.headers;
// Check to see if we got our cookies
console.log(cookie);
// Handle this as you please
if (cookie == undefined) return;
const token = cookie.split("token=")[1].split(";")[0]; // Yep, it's a string
console.log(token); // Check to see if we stored our cookie's JWT
// Some middleware:
jwt.verify(token, process.env.TOKEN, (err, user) => {
// if success upon verification,
// issue new 'token' and 'checkToken'
});
});
Done.
Please note that this a general implementation and serves as only a guide to understanding the functionality of httpOnly cookies. OP never provided original code to go off of.
I hope this helps. Godspeed.
You can't. "httpOnly" means "JavaScript cannot access it".
Using Redux-Persist would also not really help you determine if you are still logged in or if your session is timed out. That data could have been persisted weeks ago or the token could have been revoked.
The most sensible thing you can do it set up a /whoami endpoint on the server and just as a first action while your application initializes sending a request there. Either info about your user comes back -> great, save it and display it. Otherwise you get a "401 unauthorized" which means the user is not logged in and needs to log in.
We run into similar problem when improving the security of auth workflow on a project Created using React/Django
The question was: What is the best place to store JWT ?
After research we ended up implementing Oauth2 protocol, here is an article that helps you understand the logic if Refresh token rotation
Our implementation was
Generate 2 tokens on backend side (Access Token [short life] and Refresh Token [long lifespan])
Refresh token should be stored in HttpOnly cookie (as they mentioned in responses, it is not accessible on Client side by JS)
At frontend level we use only Access Token, and when it is expired, we make a call to backend to regenerate another Access and Refresh
Backend will access the Refresh Token in HttpOnly cookie and decide if it is valid to generate new Tokens
If Backend generates new valid tokens, it sends Access Token to frontend and update Refresh Token in the Cookie
Ps:
by this logic, you have no access to refresh token on frontend side, so when your Access Token no longer valid you tell the server to check Refresh Token stored in HttpOnly Cookie if it is still valid then regenerate other valid Tokens
I hope this inspires you

Why use cookie-session in addition to passport.js?

My understanding of passport.js so far is that passport.js serializes the user object and sends an ID every time to the client. I am just starting with it so sorry if it's a silly question:
Instead of express-session, I am using cookie-session as I am a beginner. My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed.
Now, I don't understand why we can't just use the passport.js ID? Why do we need to use cookie-session in addition? Also, (this may be a little unrelated, but) is the difference between session-based authentication and token-based authentication that this ID that's shared is dynamic, or changing every time? Is this still the standard and modern way of doing it in 2020?
"Instead of express-session, I am using cookie-session as I am a beginner."
using cookie session does not make anyone beginner. If you are going to store large data, use express-session, cause it stores the data in the database or redis, keeps the database id of that data, so when it gets a request, fetch the database with that id and compare the request credentials. On the other hand, cookie-session stores the data upto 4kb in the cookie on the user browser and since only user-id is stored in the cookie with passport.js, generally cookie session is used.
passport.serializeUser(
(user, done ) => {
done(null, user.id); // stores the id<4kb
}
);
When client authorizes your app, google send the responds to your callback url.
app.get("/auth/google/callback", passport.authenticate("google"))
passport.authenticate() will call req.login() this is where passport.user gets generated. req.login() will initiate the serializeUser() which determines which data of the user should be stored in the session.
passport:{user:userId}
Now this object will be assigned to req.session. so we will have req.session.passport.user
Everytime when you make a request to a server, browser automatically checks if there is cookie set related to that server and if there is it automatically attaches the cookie to the request. If you were using token based authentication, you had to manually attach the cookie to request everytime you make a request. Cookie is just transportation medium, you store data and move the data, you can even store the token in cookie. Cookie is not just related to authentication. If you have server-side project, you have to set cookie.(this is just a side node).
"My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed."
so far I explained how session is created. Now what happens when user makes a request?. In app.js file you should have two middleares.
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.initialize()) this function checks if req.session.passport.user exists, if it does it will call passport.session(). if it finds a serialized user object in the session, it will consider this req is authenticated. And then deserializeUser() will be invoked. it will retrieve the user and attach it to req.user
You don't need to use session. It is totally upto you. Just put {session: false} in route. You don't need to write passport.serializeUser and passport.deserializeUser.
cookie-session puts cookie on client system, and it is sent each time with request. passportjs search that cookie and run deserializeUser to convert it into object and attach it with request object.
express-session stores session data on the server; it only saves the session identifier in the cookie, not session data.
where as cookie-session is basically used for lightweight session applications. it allows you to store the session data in a cookie but within the client [browser]. Only use it when session data is relatively small and easily encoded as primitive values See this question for more understanding
const express = require('express');
const { Router } = express;
const router = new Router();
router
.get('/', passport.authenticate('google', { session: false }))

One database per user security

I develop an app with Ionic where each user got its own PouchDB database synchronise with its own remote CouchDB database. I use couch_peruser=true so any user that want to access its database need to authenticate.
This system is easy to do if we store the username & password locally or if we ask the user to give them anytime a sync is needed but none of these options are good (security concern or non user-friendly).
I came with those two options, but none is working:
1) The best option that came to me was to use Cookie Authentication, just save the token locally and use it, but unfortunately to connect with the token couchdb ask to use the header :
Cookie: AuthSession={TOKEN}
But this is not possible because it is a non-authorized header (unsafe) and is rejected by the browser.
2) Second option is to use couchdb Proxy Authentication but it is the same as saving the username and password as the token is valid forever.
Is there any way other way of handling authentication ? I was thinking to use an alternative users database, generate a fake password & username then send this credential to be saved into the user app. In this case if the security is compromise the user can change its password so the server can change the second password too (in the same way we revoke a token), but then there is still a problem because with the stolen credential it is always possible to access directly to the couchdb database without being seen..
Thanks for your help, i was not able to use couchdb-auth-proxy so I ended up with the following solution that has the advantage to prevent direct access to couchdb :
1) Create a node server to authenticate the user, if auth successful then return couchdb token to the app for cookie authentication
2) Create a node server used only as a couchdb proxy using node-http-proxy
with the following code :
(it is required that this router code come very early in the express middleware otherwise it might change the response and pouchdb sync does not work, so place it before app.use(bodyParser.json())
)
router.all('/*', (req: Request, res: Response, next: NextFunction) {
let token = req.get('X-Auth-Cdb-Token');
let httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({
target: target,
});
req.headers['Cookie'] = 'AuthSession='+token
proxy.web(req, res);
});
3) In your app set the pouchdb remote database with the following header :
remoteDB = new PouchDB(url, {
skip_setup: true,
ajax: {
headers: {
'X-Auth-Cdb-Token': couchdbToken
},
withCredentials: false
}
})
If you write a Progressive Web App, the Cookie Authentication is great for this, because the browser handles it for you. Use the pouchdb-authentication to log in directly to CouchDB.
On the CouchDB side, configure the Cookies as persistent, and put some longer lifetime on it. You can set it to 2 weeks, for example, so your users will only be asked for the password if they haven't logged in for two weeks.
The cookie TTL is automatically refreshed once a certain threshold is reached (I recall it's at half of the cookie TTL, so it would be refreshed if the cookie is more than a week old).
CouchDB is built for the web, so you can take advantage of it. ;-)

Working with Sessions in Express.js

I need help understanding the concept of sessions for a web application. I am running a Node.js server with Express 3.0.
My goals are to:
Create a session for each user that logs in
Store this session and use it for validating if the user is already logged in (prevent two devices using the same user at the same time) and to limit access to certain pages (by matching session ID to some other data)
I will be using MemoryStore to save the sessions (seems easiest). If the above goals make sense can you provide a thorough explanation of how to achieve them?
Express has nice examples in the github repo. One of them deals with authentication and shows how to attach the user to the req.session object. This is done inside the app.post('/login') route.
To limit access to certain pages add a simple middleware to those routes
function restrict(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
app.get('/restricted', restrict, function(req, res){
res.send('Wahoo! restricted area, click to logout');
});
As Brandon already mentioned you shouldn't use the MemoryStore in production. Redis is a good alternative. Use connect-redis to access the db. An example config looks like this
var RedisStore = require('connect-redis')(express);
// add this to your app.configure
app.use(express.session({
secret: "kqsdjfmlksdhfhzirzeoibrzecrbzuzefcuercazeafxzeokwdfzeijfxcerig",
store: new RedisStore({ host: 'localhost', port: 3000, client: redis })
}));
Use MemoryStore in express ONLY if you are not creating multiple instances (such as with the cluster module). If you are load balancing across machines, your load balancer will need to use sticky / persistent sessions.
If you meet those requirements, then all you need to do is upon login, once the credentials are validated, set a session variable to indicate logged in, for example:
req.session.loggedIn = true;
If you want to check if a user is logged in, simply check that variable.
if (req.session.loggedIn) {
// user is logged in.
}
else {
// user is not logged in.
}
You mentioned preventing a single user from having sessions more than one session at a time. To achieve that, you may need to store something in a database indicating that the user is logged in. I warn you, this can be dangerous because of stale sessions. For example, what if a user logs in, but never logs out? What if they close their browser window so the session is gone forever?
Express has no concept of an idle session expiration. I have implemented such a thing by storing all sessions in the database along with a last accessed timestamp and then periodically clean up the session data based on the time. But then you need to update your list of who is logged in as well.

Mashery IODocs - Can it support passportjs authentication from my REST API?

I am using iodocs from Mashery to be the developer front end to my REST API. My API is written with Node / Express, and uses PassportJS to authenticate the user (local strategy). My implementation requires the user to use the /login endpoint, passing in username and password. Then, Passport serializes the user in a cookie, so that subsequent requests do not need to log in.
When using iodocs, the cookie that Passport sets ("connect.sid") is not passed back in subsequent requests.
Is there a way to do this? Is there an authentication method that IODocs supports that works this way?
Cookies WILL traverse across the ports. An issue you may be encountering is that "connect.sid" is also being set by I/O Docs in that it's using the Express session.js middleware module, so that cookie value is probably getting overwritten.
Try updating I/O Docs app.js with a different cookie name in the session initializer -- setting the "key" value:
app.use(express.session({
secret: config.sessionSecret,
key: 'iodocs.connect.sid',
store: new RedisStore({
'host': config.redis.host,
'port': config.redis.port,
'pass': config.redis.password,
'maxAge': 1209600000
})
}));

Resources