Node JS / Angular 2 application , ForbiddenError: invalid csrf token - node.js

I am working on sample application using Node.js for server side and Angular 2 for front end.
To prevent CSRF attacks , I have used "csurf" middleware
Below is the relevant code to set the middleware
// cookie parser
app.use(cookieParser());
// express session middleware , this should be after cookie parser
app.use(session({secret:'clickclick'}));
app.use(session({
secret: 'clickclick',
cookie: {
path:'/',
httpOnly:true,
maxAge:null
}
}));
// CSRF middleware
app.use(csurf());
Below node.js route sets "_csrf" header
router.get('/:id/products/:pid' , wrap(function *(req , res , next) {
try
{
console.log('url' , req.url);
res.setHeader('_csrf', req.csrfToken());
let product = yield category.getProduct(req , res , next);
res.send(product);
}
catch(err)
{
res.status(500).send(err);
}
}))
The above mentioned route '/:id/products/:pid' is called from my below Angular 2 service method
// Get Product
GetProduct(id:string, pid:string):Observable<Product> {
return this.http.get('./categories/' + id + '/products/' + pid)
.map(data =>{ let headers:Headers = data.headers;
this.csrfToken = headers.get('_csrf') ;
return data.json() })
.catch(this.handleError);
}
This method assigns the _csrf header returned from server to "this.csrfToken" property.
And when the below service method makes an AJAX POST request , it uses the "this.csrfToken" property value set by above method and sets header "_csrf" value.
// Add an item to cart
AddTocart(product:Product)
{
let item = { pid:product._id , name:product.name , price:product.price , qty:1 , total:product.price };
//this.cart.push(item);
// make an AJAX call to save the item in server session
let url = './cart/add';
let headers = new Headers({'Content-Type':'application/json' , '_csrf':this.csrfToken});
let requestOptions = new RequestOptions({headers:headers});
this.http.post(url , item , requestOptions)
.map(data => {
this.cart.push(item);
}
)
.catch(this.handleError)
.subscribe( data => { });
}
Below is the Response Header of GetProduct service method.
And below is the request Header of "AddTocart" service method.
Any idea what is causing "ForbiddenError: invalid csrf token" error.
Please let me know if I need to provide more information or if the information provided is not clear.

I know this is an older question, but I'm adding this here in case someone stumbles across it in the future. Working on a similar project and encountered the same error, I fixed it by adding a XSRF-TOKEN header in the POST request, with the value taken from $.cookie("XSRF-TOKEN") (using jquery and the cookies plugin). According to the docs, _csrf should also work though.
From the project page :
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 far as I can tell, the error seems to come from POST / PUT requests including the correct cookies, but nodejs / csurf isn't looking for them there.
In your specific case, _csrf should be in the request body along with the cart items, or the header should be renamed to csrf-token, or one of the other options.

Related

How do I access Express.js Cookie in React app?

I'm creating E-shop with MERN Stack
This is my response from the server on Login where you can see, the cookie is SET and it's sent from the Backend.
That means no there shoudln't be problem with BE, but the FE I will need to handle the Cookie on the FE.
How do I access this sent Cookie from Express in the React ?
I have tried something like this:
const handleLogin = async (event) => {
event.preventDefault();
try {
const url = "http://localhost:5000/api/auth/login";
const data = await axios.post(url, formFields);
const { user } = data.data;
// Here I have tried to access it from Headers where I can see it in the attached
// picture under Set-Cooki. But inside headers I can only see
// Content-Length and Content-Type
console.log(data.headers);
// Aswell I have tried react-cookie but docs are not clear enough for me.
// and this just sets cookie from react, I would like to use cookie from express
// and set it like this
// await setCookie("user", "INSERT_HERE_COOKIE_FROM_EXPRESS.JS", {
// path: "/",
// });
setCurrentUser(user);
await resetFormFields();
} catch (error) {
const data = error.response.data;
!data.error
? alert(`${data.message}`)
: alert(`${data.message}: ${data.error}`);
}
};
Thank you for any answers, I'm sure it's not that hard as I think and it's few lines of code.
As I see on your screenshot - you use express with httpOnly cookies:
https://developer.mozilla.org/ru/docs/Web/HTTP/Cookies -
A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API; it's only sent to the server. For example, cookies that persist in server-side sessions don't need to be available to JavaScript and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks.
And I think you don't want to use nonsecure cookies in your E-shop - so you can't access it, but you can use custom headers, so on your frontend it will be like:
fetch('/myapi').then(response => console.log(response.headers.get('myCustomHeader')));

Set response header along with a string

I am trying to send the token in the headers of an HTTP request from backend to the frontend along with sending my own defined string. However, I am getting an issue. The token is being printed as null on the client-side. I don't know why:
Here's my code:
Node/Express
if (bcrypt.compareSync(passcode, results[0].password))
{
const token = jwt.sign({id: email}, secret, {expiresIn: 86400 });
console.log(token);
if(results[0].userrights == 'full')
{
res.setHeader('x-access-token', token);
res.send("Full Authorization");
}
//rest of the code
}
Angular
this.http.post('http://localhost:3000/api/login', form.value, {responseType: "text", observe:
'response'})
.subscribe(responseData => {
console.log(responseData);
console.log(responseData.headers.get('x-access-token')); //prints null on the console
I have searched quite a bit and found different examples which is making it very confusing. I don't want to use response status rather my own defined string. I have tried different things to print the variable but it still is throwing as null.
If you are using a browser extension to allow CORS requests then Access-Control-Expose-Headers should be added to the headers on server side. Please try adding the following line: res.setHeader('Access-Control-Expose-Headers', '*')
Angular2 's Http.post is not returning headers in the response of a POST method invocation
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

Angular 8 / NodeJS CORS error: Redirect location disallowed

I am using Angular 8 with a NodeJS backend in a lab environment. I have a button in Angular which sends a value to the backend server using a POST request. When the value is received, the backend reaches out to an API server based on the value from the form which returns a status code in JSON which I need to use for conditionals in the frontend.
The Angular button logic looks like this:
this.form = this.fb.group({
ticket: [''],
})
}
const testTicket = this.form.value.ticket
submitForm() {
this.http.post('http://backend.com:8090/api/backendquery?testTicket='+ testTicket ,{}).subscribe (
(response) => {
console.log(response)
if(response != 'failure') {
this.ticketFound=true
this.ticketNotFound=false
status = 'Success'
} else {
// if(response == null) {
this.ticketNotFound=true
this.ticketFound=false
status = 'Failed'
}
})
}
The backend code reaching out to the API server looks like this:
var cors = require('cors');
app.use(cors());
app.post('/api/backendquery', function(req, res){
res.redirect('https://username:password#myserver.com/ticketquery='+req.query.testTicket);
});
I passed the credentials in the URL since the API server requires authentication.
I obtain the results successfully from Postman using the following URL:
http://backend.com:8090/api/backendquery?testTicket=12345
However, when using the browser by submitting the value, I get the following error on console:
'http://backend.com:8090/api/backendquery?testTicket=12345' from origin 'http://frontend:4200' has been blocked by CORS policy: Redirect location '' contains a username and password, which is disallowed for cross-origin requests.
As you can see in the backend code, the CORS package is already enabled which should take care of CORS errors. I aware that credentials should not be present in URLs, however, what could be take best approach in my situation?
The answer was that instead of redirecting in the backend, the GET request was done using the "required" package and then saved to a variable which was then forwarded back to the frontend.

How to include access-token in the HTTP header when requesting a new page from browser

The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:
How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?
Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?
I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.
By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.
By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.
exports.customer_profile = function (req, res) {
var token = req.headers.token;
var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
var decoded = jwt.verify(token, public_key);
var sql = 'SELECT * FROM customer WHERE username = "' + decoded.sub + '"';
util.conn.query(sql, function (err, rows) {
if (!err) {
for (var i = 0; i < rows.length; i++) {
res.render('customer_profile', {customer_profile: rows[i]});
break;
}
}
});
};
I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.
So far what I was able to figure out is the following:
Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
// Get current user's ID Token
firebase.auth().currentUser.getIdToken()
.then(token => {
// Make a fetch request to 'path'
return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
method: 'GET',
headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
});
})
.then(response => {
// As noted below, this part I haven't solved yet.
// TODO: Open response as new webpage instead of displaying as data in existing one
return response.text();
})
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
}
Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup).
// getLocale - language negotiation.
// getContext - auth token verification if it is available and appends it to Request object for convenience
app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);
// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
if(!idToken) {
return next(); // Passes to next middleware if no token, terminates further execution
}
admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
.then(token => {
req.decoded_token = token; // Append token to Request object for convenience in further middleware
return next(); // Pass on further
})
.catch(error => {
console.log('Request not authorized', 401, error)
return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
});
}
Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user
router.get('/console', middleware.getTranslation('console'), (req, res) => {
if(req.decoded_token) { // if token was verified successfully and is appended to req
res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
} else {
res.status(401).send('Not authorized'); // else send 401 to user
}
});
As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access
What I have not solved yet:
As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.
Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet

Setting cookie for request-url, when origin url and request url are different

Giving some code that I have tried so far.
Request-origin - "abc.com", Request-url - "login.abc.com/login" (Post method)
function setCookie(req, res)
{
//Some code goes here.
//Code to set cookie
res.cookie('test',"some value"); //This should set cookie for login.abc.com, which never happened
}
Also tried to set for domain
function setCookie(req, res)
{
//Some code goes here.
//Code to set cookie
res.cookie('test',"some value",{domain:'.abc.com'},{'path' : '/'});//This should set cookie for .abc.com
}
Following code is working as expected
Request-origin - "abc.com", Request-url - "abc.com/login" (Post method)
function setCookie(req, res)
{
//Some code goes here.
//Code to set cookie
res.cookie('test',"some value",{domain:'.abc.com'},{'path' : '/'});//This set the cookie for .abc.com successfully
}
Can someone help me to understand why it is not setting cookie in the first case but it is setting cookie in the second case?
NOTE I am trying this in my local.
So finally I got the solution to my issue and able to achieve what I want. Writing this answer, so that some one else can get benifit in the future.
Changes in client side:-
$.ajax({type: "post",
data: {},
timeout: 30000,
dataType:"json",
xhrFields : {withCredentials : true} //Need to add this along with the request.
})
Now server side changes:-
var cors = require('cors');
app.post('/testRoute',
cors({credentials:true,
origin:'http://example.com'}), //Origin should be the url of the page, from where we are sending request.
function(req, res){
res.cookie('test',"some value",{'path' : '/'})
})
Refer cors-doc to know more about how to use cors in server side.
NOTE:- Can not use Access-Control-Allow-Origin value as * if xhrFields : {withCredentials : true} we are specifying in the ajax request.

Resources