Using a MEAN environment (with express 4), I create a cookie like this.
//webserver.js
app.use(cookieParser(„somesecretkey“));
//somescript.js
res.cookie(‚testcookie‘, ‚testvalue', {signed: true, maxAge: 999999, httpOnly: true});
In another script, I try to check the existence of the cookie like this.
//someotherscript.js
if(req.cookies.testcookie){
console.log("COOKIE EXISTS“+req.cookies.testcookie);
}else{
console.log(„NO COOKIE“+req.cookies.testcookie); //always undefined
}
I checked the browser for the cookie and it definitely exists but the console keeps logging that there is no cookie (cookie undefined) when I press refresh or simply visit the page. As soon as I change the cookie to unsigned and remove the secret key, I can access it!? Why can’t the cookie be found once its signed?
The expressjs documentation for res.cookie tells us:
When using cookie-parser middleware, this method also supports signed
cookies. Simply include the signed option set to true. Then
res.cookie() will use the secret passed to cookieParser(secret) to
sign the value.
res.cookie('name', 'tobi', { signed: true });
Later you may access this value through the req.signedCookie object.
So:
did you specific a secret using cookieParser?
you should check for the cookie in req.signedCookie, not req.cookies
Related
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
I am using Node JS with express-session.
One question answered here advises to use req.cookies['connect.sid'] to get the session ID. Another answer suggests I use req.sessionID
When I compare the two the req.cookies['connect.sid'] has a string like the following:
s:G1wOJoUAhhemRQqCs7dAGlMIk5ZGaJUg.z1/HrHTfndRqKpXza8qWuwHLS067xrWfVgqTDDGYoos
req.sessionID has a string like the following:
G1wOJoUAhhemRQqCs7dAGlMIk5ZGaJUg
If the second string is the session ID (G1wOJoUAhhemRQqCs7dAGlMIk5ZGaJUg), what is the other information in the connect.sid cookie?
Tried looking for the answer via google and other websites with no luck.
Thanks,
Darren
express-session stores all session information server-side. If you use an sql database, you'd have a table for your sessions, that would look like this :
sid | sess | expire
R02GJn2GoyaqRyten1BEGbHa0XCbj529 | {"cookie": "originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"mybool":true,"userid":16}
That's the answer to your question, and a short explanation of what the data means, sessionID is just a (primary) key to access the data that is only available server-side.
Now from your question it looks like you're planning on using express-session wrong.
To use express-session on your express server you would include it like so :
const session = require('express-session');
app.use(session({
name : 'mycookie',
secret : 'mysecret',
saveUninitialized : false,
resave : true
}));
and every request that goes into all subsequent routes after this will have a .session attribute. In any case, you should never need to access the session id yourself, the middleware itself authenticates every request against whatever store you used for express-session.
app.get('/givemeasession',function(req,res,next){
req.session.mybool = true;
req.session.somedata = 'mystring';
req.session.evenobjects = { data : 'somedata' };
res.send('Session set!');
});
app.get('/mysession',function(req,res,next){
res.send('My string is '+req.session.somedata+' and my object is '+JSON.stringify(req.session.evenobjects));
});
Bottomline : you shouldn't ever need to access the cookie yourself, or do anything with it, because any cookie auth is automatically handled by the middleware.
The accepted answer is correct by saying, don't handle cookies and sessions by yourself, but I don't think it answers the question:
The second part in connect.sid is the cookie signature.
In case of express-session this is generated by the package cookie-signature with the secret you set when you set up the session middleware.
As you can see in the source-code, the very first step is verifying if the cookie was signed by the secret. Afterwards the cookie is retrieved from the store.
(In case of the memory store, the retrieval also deletes the cookie if expired)
Source: https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L542
I want to retrieve values from my cookie.
I am using passport.js, and In this quest, I have tried different things.
I run req.cookies; which gives me this:
's:x05d6V5Dhf6efFGjIkO26Ka1.imN4lT7OhW83Nc9Z7vw5dFbCoQhanP3aa37iyNWQyvU' }
Is this a signed cookie or an encrypted cookie?
The 's:' in the beginning makes it seem like a signed one, however when I ran req.signedCookies;, I got back an empty object {}. So is this cookie signed or encrypted?
I then ran: req.sessionID; which gave back this:
x05d6V5Dhf6efFGjIkO26Ka1
As you can see, this is also contained in my cookie, before the dot. So I guess the sessionId is stored in my cookie, right?
Then I ran req.secret; which in turn returned undefined.
Is the secret in the session, which is declared here:
app.use(express.session({ secret: 'blablablabla' }));
used to sign the cookie or to encrypt it?
The cookie-signature module can only unsign a cookie and not decrypt them - is that correct?
It can unsign it based on the previous session secret?
Finally, where is this cookie stored? In my mongo db?
The part after the dot:
imN4lT7OhW83Nc9Z7vw5dFbCoQhanP3aa37iyNWQyvU
is the signature of the session id:
x05d6V5Dhf6efFGjIkO26Ka1
That is to say, the session id is encrypted with the secret and append to the session id with a dot to compose the cookie.
The secret is not contained in the cookie.
The cookie-signature module can unsign a cookie if you provided the right info:
cookie-signature.ungisn(`${the_original_sessionid}.${the_encypted_sessionid}`,secret)
It's more for my understanding. Why is there a cookie object in the req.session in express?
I use req.session to store the login status and username.
The cookie object in req.session is the same as the cookie properties in the client cookie, placed by express.parseCookie but without sessionID.
Can anyone explain why this is in there?
It cant be for the purpose to identify cookie and session because thats already made by the cookie value and the session ID (req.session.ID), or am I'm wrong?
- req.session -->
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
loggedIn: true,
username: 'luidpold' }
For convenience. It’s the cookie associated with the session, and you can query/modify it.
The session handler in Express comes from Connect. The documentation for Connect says:
Session#cookie
Each session has a unique cookie object accompany it. This allows you
to alter the session cookie per visitor. For example we can set
req.session.cookie.expires to false to enable the cookie to remain for
only the duration of the user-agent.
Session#maxAge
Alternatively req.session.cookie.maxAge will return the time remaining
in milliseconds, which we may also re-assign a new value to adjust the
.expires property appropriately.
cookieParser() gives us the option of signing cookies with a secret sentence, which is great to prevent tampering. I understand that a cookie is signed with a special value, to prevent tampering.
I just discovered cookieSession(), which I find to be a great alternative to server-stored cookies (I only store { loggedIn = true, userId=763487246824632}, it never grows).
But... I found that setting a "secret" for cookieParser() breaks things, and cookieSession() stops working if the secret sentence matches.
The reason seems to be that if the cookie is signed using the same secret, then cookieParser() actually takes it and parses it. The strange thing is that once cookieParser() has done its work, and with the same signature secret, the session is set to:
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
Rather than:
{ testing: 'OOO' }
(Each reload adds an 'o')
So...
Did my analysis get it right?
Do you know why the session is set to that strange { cookie object if the secret sentences match?
Merc.
Your analysis is correct, I can reproduce it.
The issue is caused by this line in the cookieSession middleware (some context: options.secret is the key passed to cookieSession, req.secret is the key passed to cookieParser): if you pass both middleware a secret key, cookieSession assumes that it will find the raw (unparsed) cookie in req.cookies.
But since cookieParser has picked up the signed cookie as well (and it's being run before cookieSession), it has parsed the cookie itself (and because the signing keys were the same, it succeeded to do so), stored it in req.signedCookies and deleted it from req.cookies. So as far as cookieSession is concerned, the cookie just isn't set.
The object you see is the default session contents (which is the cookie property from the cookieSession configuration):
app.use(express.cookieSession({
cookie : { // <-- this object
...
}
});
As for a solution: either use a different key for each middleware, or just pass one of them your secret key, but not both (with the understanding that if you pass it to cookieParser, all your cookies will be signed).
FWIW: I'm not entirely sure if this is a real bug. It's a consequence of using the same signing mechanism for both cookieParser and cookieSession, with no distinction between cookies signed by one or the other. Although it could be fixed by always checking if the cookie is located in req.signedCookies.