Node express static with csrf - node.js

I'm trying to use csurf in nodejs, express and React Project. My csurf is working fine right now, but I want to double check if I did it right.
Here is my nodejs router and middleware structure:
app.use(cookieParser());
app.use("/api/...") // routes that don't need csrf
app.use("/form/...",csrf({cookie:true})) // form path with csrf middleware
app.use(express.static("/img")) // image folder which doesn't need csrf
app.use(csrf({ cookie: true })); // enable csrf for the rest of the app
app.all("*", function(req, res, next) {
res.header("X-CSRF-Token", req.csrfToken()); // set csrf to header
return next();
});
app.use(express.static("/SPA")); // frontend project
Current behavior:
When I first enter my web project, I have X-CSRF-Token: xxxxx and set-cookie: _csrf=yyyyy; Path=/ in my response headers
When I refresh my page, Cookie: _csrf=yyyyy; appears in the request headers section.
When I refresh my page, X-CSRF-Token changes to a different value.
Only X-CSRF-Token value passed through post request, _csrf value inside cookie throw 403.
Question:
A. I believe setting app.use(csrf({ cookie: true })) is redundant, but when I set it to false or removed app.all(...) part, the app throw 403 / Internal Server Error. How to fix it?
B. X-CSRF-Token changes everytime I refreshed my page, it's obviously a normal behavior since I put it in the header, but does it defeat the purpose of csrf? Since my project is SPA, do I really care that much?
Please point out if there were anything wrong with the logic / behavior ?
Thanks.

Since my project is SPA, do I really care that much?
If a client app (SPA or not) sends some data to the backend and the data changes backend's state either directly or indirectly, for example via an action performed by backend on client's behalf, then CSRF vulnerability exists and you should care unless SPA framework like Angular takes care of CSRF protection. Using SPA doesn't change anything with respect to CSRF, it doesn't help and it alleviates nothing.
With cookie-parser middleware the csrf middleware works like that:
Checks for its cookie with a predefined name in the incoming request. If not found then generates a secret key and puts its value (decorated a bit) into the response cookie hoping to find it in the next request. So the secret is not a secret anymore.
If cookie not found and the incoming request is mutating like POST (e.g. not GET, HEAD ...) then fail it e.g. send 403 back with cookie set. If non-mutating like GET then processing is finished.
If cookie found then check for the second piece of data, by default in few places including HTTP header with a predefined name. If not found or found and incorrect then fail incoming request. Otherwise processing is finished.
To ensure this check is successful you are responsible for 2 steps:
- on the backend call req.csrfToken() to obtain this second piece of data and store in the response. You have chosen to store it in HTTP header, it's fine. But you could have used any header name. Or you could have used <meta> tag in the <head> section.
- on the client take the second piece of data from the above header or <meta> tag in the backend response and put it into the request you are about to send assuming the request is mutating e.g. POST, PUT, etc. Furthermore, you need to put it into one of the predefined places in the request where csrf middleware searches by default.
Regarding your code:
1. The client code responsible for the second step is missing.
2. On the backend call csrf({options}) function once and store the returned value. You have called it twice. The return value, let's call it retValue, is the configured csrf middleware, use it as needed:
app.post(/<path>, retValue, ...req, res, next) => {...
3. As for the options, set httpOnly: true. Additionally, in production set secure: true:
csrf({cookie: {
httpOnly: true,
secure: true
}})

A. I believe setting app.use(csrf({ cookie: true })) is redundant,
but when I set it to false or removed app.all(...) part, the app
throw 403 / Internal Server Error. How to fix it?
By setting app.use(csrf{cookie:true}) before your routes you tell your app to pass each request that you get through csrf middleware (except those higher than this command). This middleware is supposed to do 3 things.
First is to set csrf cookie, if it's not present yet. This cookie is a secret which is needed to create/verify csrf tokens.
Second is to create new token when you call req.csrfToken().
Third is to verify tokens for all non-GET/HEAD/OPTION requests.
Also it's important to understand what you do with this command:
app.all("*", function(req, res, next) { res.header("X-CSRF-Token",
req.csrfToken()); // set csrf to header return next(); });
All your requests (get, post, put etc etc) they generate new token on the basis of the secret you have in your cookie. Same secret generates random tokens, as random salts are used for this purpose.
So in case you remove app.all part with req.csrfToken() you will not be generating any tokens, so verification will fail. And in case you remove app.use(csrf) part you will not be able to verify anything as this part does verification and you will fail to issue tokens as well. Because in other words the middleware will be turned off. So you can't remove any of these two commands as they serve different purpose.
B. X-CSRF-Token changes everytime I refreshed my page, it's obviously a normal behavior since I put it in the header, but does it defeat the purpose of csrf? Since my project is SPA, do I really care that much?
The purpose of csrf tokens is to verify whether request came from your website, because otherwise the client will have cookie only, but no token in the header. Pressing link initiating some actions on your website, but being on a different website will not generate csrf token for the client.

Related

CSRF - invalid token: Capacitorjs App with Node.js backend

In building a mobile app with capacitorjs from a functional web app with Node.js backend that uses the csurf library for the prevention of CSRF attacks, I am getting invalid token errors. The backend is being served from a local machine over http, for now. CORS is setup on the routes under test.
As I understand it, the csurf library provides the client two pieces of information, a secret in the form of a cookie, and a token, both of which the client sends back when making a POST request. The library then checks the expected value of the token with the calculated value (from the secret and from part of the token), and looks for them to match. (Aside: Getting the client to send back the cookies required some effort: I could make it work with a POST using form submission, but not with jQuery $.ajax.) At any rate, I was able to get the client to send back the secret (as a cookie in form submission or otherwise in AJAX POST) and the CSRF-Token, but they don't check out, i.e., the csurf library seems to expect a different answer, for example, here's an example of what the cookie and token look like in one instance:
req.cookies: { _csrf: 'W66Nq2CMcTTJeiFjJRu0gWFH' }
_csrf: 'guOiwkW0-BZguE8LKmYvW3ArKXhUGZuFN_88'
I put some log statements inside the function that checks these two values for correspondence in the csrf library that is used by the csurf library, and got this result:
csrf: secret W66Nq2CMcTTJeiFjJRu0gWFH
csrf: token guOiwkW0-BZguE8LKmYvW3ArKXhUGZuFN_88
csrf: expected guOiwkW0-cWREHVwpgMGiIMoKiSJmc4bA3HE
This kind of result happens whether I make a POST as form submission, or as an AJAX request. For the time being then, I have disabled CSRF protection on these cors routes serving the capacitorjs app.
Another issue that has me concerned is that the folks who wrote this library have this advice:
Don't ever create a GET /csrf route in your app and especially don't
enable CORS on it. Don't send CSRF tokens with API response bodies.
in Understanding CSRF. I am wondering how to send CSRF Tokens to the client, if not in API response bodies!

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

What is the optimal way to secure JWT in cookies for a React/Redux application?

Right now I'm working on a React/Redux full stack application and I'm working on setting up JWT auth and passing the JWT via cookies. I had a couple of questions on handling the CSRF token generated by the server.
1) Once you set the CSRF token on the server side, does it then get passed to the client? How exactly does it get passed? (I've seen examples where its passed in as an object, but the ones ive found weren't explained very well or just scarce)
server.js
// config csrf in server
app.use(csrf({
cookie: {
key: '_csrf',
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 86400
}
}))
// Hits my api routes, and if these arent hit, the index.html file is rendered
app.use(routes)
// Route used to fetch the index html file
app.get('*', (req, res) => {
let csrfToken = req.csrfToken()
console.log(csrfToken) // This doesnt console log anything on the server side
res.sendFile(path.join(__dirname, "./client/build/index.html"), {
_csrf: csrfToken
})
})
2) Once the CSRF token is set, should it be stored in the state of the application (Redux store) for persistent storage? Or would this be unnecessary?
3) On the client side, when I'm ready to submit data to a route via POST request, if I understand correctly, you'd have to include the input hidden field with the csrf variable like so:
<input type="hidden" name="_csrf" value=csrfToken/>
So when you submit a form, you'd include that input, then in the post request (assuming fetch or axios), you'd set the headers to include that csrf token, that way the server can compare it to the token the client is submitting to the route, am I understanding this correctly?
Thank you!
None of what you’ve asked specifically relates to react or redux. This is about request / response exchange with an HTTP server.
When using the the CSRF middleware, it automatically provides a CSRF tokens as a session cookie on request, and you’re expected to provide that back to the server on further requests.
1) CSRF tokens are usually set in a cookie by the server, or set as meta tag in the HTML. This code is generated uniquely per HTTP request to the server. It’s the applications responsibility to read the cookie/meta tag and then send it back to the server for future requests. One possible way is as a header: ‘X-csrf-token’ (https://en.m.wikipedia.org/wiki/Cross-site_request_forgery)
2) unnecessary in some cases: but that’s only by virtue of the fact that if the server sends a cookie header response, then your browser will always store that cookie, unless you have cookies turned off. When sending a request to the server, you need to retrieve that cookie and send it as the header above
3) I suggest you read the readme for express CSRF middleware (https://github.com/expressjs/csurf/blob/master/README.md), particularly this part about how the middleware looks for the CSRF tokens in responses or further requests:
The default value is a function that reads the token from the
following locations, in order: • req.body._csrf - typically generated
by the body-parser module. • req.query._csrf - a built-in from
Express.js to read from the URL query string.
• req.headers['csrf-token'] - the CSRF-Token HTTP request header.
• req.headers['xsrf-token'] - the XSRF-Token HTTP request header.
• req.headers['x-csrf-token'] - the X-CSRF-Token HTTP request header.
• req.headers['x-xsrf-token'] - the X-XSRF-Token HTTP request header.
As you can see - how you provide it back to the server is up to you — most people choose a header, but it can be as a body or a get request query string.

Cookies not sent on redirect

I am building a react web application with a separate back-end express api that manages all the calls, including passporting and setting cookies. Let's call the back-end service 'api.com' and the front-end service 'react.com'. I'm using passporting with an existing provider (spotify) and after the authorization succeeds, a cookie is set on api.com. The idea is that the user interacts with react.com and requests are made to api.com via a proxy.
If I'm just testing in my browser and I make a call to api.com/resource, the cookie is automatically set. I know this because I've added a bit of logging and also because the requests that require authorization are succeeding via the cookie.
However, when I make calls to api.com from react.com via the proxy, the cookie is not set. Is this expected behavior when proxying? It seems odd that the cookie is set when I call api.com directly, but it is not set when it is redirected. Is there a way around this? My thought would be to communicate the cookie from api.com to react.com, save it there, and send it on all subsequent requests, but that seems overkill. I'm also wondering if maybe I should be setting the cookie on react.com instead of api.com.
I've tried in both Firefox and Chrome, and if it makes a difference, I'm using axios for the requests on react.com.
const request = axios({
method:'get',
url:'/api/resource'
});
This gets proxied as follows (still on react.com), using express-http-proxy:
app.use('/', proxy('api.com', {
filter: (req) => {
return (req.path.indexOf('/api') === 0);
}
}));
But once this hits api.com, any authentication fails, because the cookie is not present.
Any help is appreciated
As far as I have understood your question, I think you're not considering that cookies are set to host name.
So in the first case the hostname is same and its okay, but in the second case the browser's cookies are not set for react.com
So trying to set the cookie on react.com should work.
I would have asked for a clarification using a comment but I don't have enough reputation for that yet.

Protect non-api (res.render) route with express-jwt token in Node.js

First of all, I have read all tutorials on protecting REST API routes with jwt (express-jwt & jsonwebtoken), and it works fine for that purpose.
This works fine:
app.use('/api', postApiRoute);
And this also works, somewhat, I mean.. it does verify the token when I use it to show a webpage with angular http request calls, but when you add expressJwt({secret: secret.secretToken}), you cannot just access localhost:3000/api/post anymore. The expressJwt({secret: secret.secretToken}) is the problem here.
app.use('/api', expressJwt({secret: secret.secretToken}));
app.use('/api', userApiRoute);
What I really need is to protect a non-json but html/text request route with jwt like eg.:
app.get('/admin*', expressJwt({secret: secret.secretToken}), function(req, res){
res.render('index', {
//user: req.session.user, <- not sure how to do the equivalent, to extract the user json-object from the express-jwt token?
js: js.renderTags(),
css: css.renderTags()
});
});
.. without having to make http requests in angular/js, but using express' render function.
I need to do this since my application has 2 primary server routed views, so 1 where admin scripts are loaded from, and 1 where the frontend (theme) assets gets loaded.
I cant however get jwt/tokens to work with server rendered views, only json api requests.
The error i'm getting is: "UnauthorizedError: No Authorization header was found"
Couldn't find any information about (server rendered views protected with jwt, only serverside api requests and client side angular/ajax http requests) this, so I hope my question is clear, and that I do not have to fall back to using sessions again.
Not sure if I understood correctly, but if you are talking about entry html routes (i.e., loaded directly by the browser and not by you angular app), then you simply have no way of instructing the browser as to how to set the authorization header (no without introducing some other redirect based auth flow).

Resources