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!
Related
I'm making private homepage working only for login user.
When user login success, API server return token and save it to cookie.
After that, token was added to all fetch's request header at server-side "handleFetch" hooks.
All works fine with server-side rendering. But At client-side rendering needed position, I can't use "load's fetch" and also can't get token from cookies by default sveltekit functionality.
How can I solve this?
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.
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.
okay.
I think I have failed to understand an elemental part of token based authentication.
I am using node with express and am using jwt to prevent access to my site if you haven't logged in. I can create a token on the login page, and I can send it back to the client and store it in localStorage/cookie. Now if the user wants to navigate to another page they will type in a url and trigger a get request.
How do I access that token from localStorage/cookie and pass it to the server before I load the page as part of the get request. My assumption is that there should be a way of passing the token to the server - intercepting it in the middleware - and loading the page if the token is legit, or redirecting to the login page if the token isn't validated correctly.
On a post request this would be much simpler as you can fetch the token and pass it as part of an ajax call, after the page has loaded.
I have seen references to including the token as part of the request header (authorization bearer). I assume this only works for post, because if you were able to set the header parameter 'globally' then why would you bother storing on the client side in a cookie/localStorage.
So as you can see I am a little confused by the workflow. It seems like I am going against the grain somehow. Any clarity would be much appreciated.
If you are using localStoage in order to store the JWT, then the easiest way to pass it to the server is by retrieving first the token from the localStorage with localStorage.getItem('token') (or whatever your token name is) and then inserting it in the header of the request (either it is GET or POST/PUT/DELETE). Depeding on the library you are using to handle your http requests on the client, there are different ways of doing so. In jQuery for example, you can do the following inside the AJAX request:
$.ajax({
url: API_URL + "/endpoint",
method: "GET",
beforeSend: function(request){
request.setRequestHeader("Authorization", "BEARER " + localStorage.getItem('token'));
}
})
After this, on the server side simply access the parameters by accessing request.header options just as you would normally do. Hope this helps!
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).