I'm trying to setup CSRF tokens so that I can do a number of checks before issueing a token to the client to use in future requests.
Taking the guidance from the csurf documentation, I've setup my express route with the following:
const express = require('express');
const router = express.Router({mergeParams: true});
const csurf = require('csurf');
const bodyParser = require('body-parser');
const parseForm = bodyParser.urlencoded({ extended: false });
const ErrorClass = require('../classes/ErrorClass');
const csrfMiddleware = csurf({
cookie: true
});
router.get('/getCsrfToken', csrfMiddleware, async (req, res) => {
try {
// code for origin checks removed for example
return res.json({'csrfToken': req.csrfToken()});
} catch (error) {
console.log(error);
return await ErrorClass.handleAsyncError(req, res, error);
}
});
router.post('/', [csrfMiddleware, parseForm], async (req, res) => {
try {
// this returns err.code === 'EBADCSRFTOKEN' when sending in React.js but not Postman
} catch (error) {
console.log(error);
return await ErrorClass.handleAsyncError(req, res, error);
}
});
For context, the React.js code is as follows, makePostRequest 100% sends the _csrf token back to express in req.body._csrf
try {
const { data } = await makePostRequest(
CONTACT,
{
email: values.email_address,
name: values.full_name,
message: values.message,
_csrf: csrfToken,
},
{ websiteId }
);
} catch (error) {
handleError(error);
actions.setSubmitting(false);
}
Postman endpoint seems to be sending the same data, after loading the /getCsrfToken endpoint and I manually update the _csrf token.
Is there something I'm not doing correctly? I think it may be to do with Node.js's cookie system.
I think your problem is likely to be related to CORS (your dev tools will probably have sent a warning?).
Here's the simplest working back-end and front-end I could make, based on the documentation:
In Back-End (NodeJS with Express) Server:
In app.js:
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
const cors = require('cors');
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })
var app = express()
const corsOptions = {
origin: "http://localhost:3000",
credentials: true,
}
app.use(cors(corsOptions));
app.use(cookieParser())
app.get('/form', csrfProtection, function (req, res) {
res.json({ csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function (req, res) {
res.send('data is being processed')
})
module.exports = app;
(make sure you update the corsOptions origin property to whatever your localhost is in React.
In Index.js:
const app = require('./app')
app.set('port', 5000);
app.listen(app.get('port'), () => {
console.log('App running on port', app.get('port'));
});
In React:
Create file "TestCsurf.js" and populate with this code:
import React from 'react'
export default function TestCsurf() {
let domainUrl = `http://localhost:5000`
const [csrfTokenState, setCsrfTokenState] = React.useState('')
const [haveWeReceivedPostResponseState, setHaveWeReceivedPostResponseState] = React.useState("Not yet. No data has been processed.")
async function getCallToForm() {
const url = `/form`;
let fetchGetResponse = await fetch(`${domainUrl}${url}`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"xsrf-token": localStorage.getItem('xsrf-token'),
},
credentials: "include",
mode: 'cors'
})
let parsedResponse = await fetchGetResponse.json();
setCsrfTokenState(parsedResponse.csrfToken)
}
React.useEffect(() => {
getCallToForm()
}, [])
async function testCsurfClicked() {
const url = `/process`
let fetchPostResponse = await fetch(`${domainUrl}${url}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"xsrf-token": csrfTokenState,
},
credentials: "include",
mode: 'cors',
})
let parsedResponse = await fetchPostResponse.text()
setHaveWeReceivedPostResponseState(parsedResponse)
}
return (
<div>
<button onClick={testCsurfClicked}>Test Csurf Post Call</button>
<p>csrfTokenState is: {csrfTokenState}</p>
<p>Have we succesfully navigates csurf with token?: {JSON.stringify(haveWeReceivedPostResponseState)}</p>
</div>
)
}
Import this into your app.js
import CsurfTutorial from './CsurfTutorial';
function App() {
return (
<CsurfTutorial></CsurfTutorial>
);
}
export default App;
That's the simplest solution I can make based on the CSURF documentations example. It's taken me several days to figure this out. I wish they'd give us a bit more direction!
I made a tutorial video in case it's of any help to anyone: https://youtu.be/N5U7KtxvVto
Related
I am using JWT token for authentication in my MERN app. when i click on this endpoint "http://127.0.0.1:1000/api/v1/users/login" in postman to login a user and this endpoint "http://127.0.0.1:1000/api/v1/users/verify" to verify a user's token, the token is returned. However, when i do the same operation in my react frontend app, the token returns undefined. Please what am i doing wrong? Here is my React code. P.S the token is stored in the cookies.
function Welcome() {
const [user, setUser] = useState("");
const sendRequest = async () => {
const res = await axios
.get("http://127.0.0.1:1000/api/v1/users/verify", {
withCredentials: true,
})
.catch((err) => console.log(err));
const data = await res.data;
console.log("RESPONSE", data);
return data;
};
React.useEffect(() => {
sendRequest().then((data) => console.log(data));
}, []);
console.log(user);
return <div>Welcome</div>;
}
Here is the Verify token code in my express app
exports.verifyToken = async (req, res, next) => {
const cookies = await req.headers.cookie;
const token = cookies.split("=")[1];
if (!token) {
res.status(404).json({ message: "no token found" });
}
jwt.verify(String(token), process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(400).json({message: "Invalid token" });
}
req.id = user.id;
});
next();
};
User cookie parser
const express = require("express");
const app = express();
const cookieParser = require("cookie-parser");
app.use(express.json());
app.use(cookieParser());
app.get("/", (req, res) => {
res.setHeader("Set-Cookie", "name=langesh;SameSite=None;");
res.send("hello");
});
app.get("/get-cookie", (req, res) => {
res.send(req.cookies);
});
app.listen(9000);
output
{name : "langesh"}
I personally don't have much experience with axios, I prefer fetch() method, it works perfectly.
That said, I think this issue could arise because token isn't identified in the request. Try adding headers: { Authorization: `Bearer ${token}`} to your get method and see if it is fixed.
Your code should look like :
axios.get("http://127.0.0.1:1000/api/v1/users/verify", {
headers: {
Authorization: `Bearer ${token}`
},
withCredentials: true,
})
Before I deploy, the app performed fine on localhost. But since I deployed my frontend (react) to Netlify and backend(node/express + mysql) to Heroku, all requests sent from the frontend started to get blocked by CORS policy, with the error message:
"Access to XMLHttpRequest at 'https://xxx.herokuapp.com/login' from origin 'https://xxx.netlify.app' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://xxx.app/' that is not equal to the supplied origin."
Most importantly, the value of my Access-Control-Allow-Origin header is literally the same as the origin stated.
Originally, I've tried to use a wildcard ("*"), but it seems that due to the withCredential problem, the system just can't allow that kind of vague statement.
I've also seen many people using Netlify.toml to tackle some configuration problems, but seems ineffective for me.
Is it the header's problem? If not, then what is the problem?
I really want to know what I should do to solve this error...
The console window of the app deployed:
Cors Error
My index.js in the server folder:
const express = require('express')
const mysql = require('mysql')
const cors = require('cors')
const session = require('express-session')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const port = 3010
const app = express()
app.use(express.json())
app.use(cors({
origin: ["https://xxx.app/"], // the link of my front-end app on Netlify
methods: ["GET", "POST"],
credentials: true
}))
app.use(cookieParser())
app.use(bodyParser.urlencoded({
extended: true
}))
app.use(
session({
key: "userId",
secret: "subscribe",
resave: false,
saveUninitialized: false,
cookie: {
expires: 60 * 60 * 24
},
})
)
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "https://xxx.netlify.app/"); // the link of my front-end app on Netlify
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PATCH, DELETE, OPTIONS"
);
res.setHeader('content-type', 'application/json');
next();
});
const db = mysql.createPool({
// create an instance of the connection to the mysql database
host: 'xxx.cleardb.net', // specify host name
user: 'xxx', // specify user name
password: 'xxx', // specify password
database: 'heroku_xxx', // specify database name
})
...
app.get('/login', (req, res) => {
if (req.session.user) {
res.send({
isLoggedIn: true,
user: req.session.user
})
} else {
res.send({
isLoggedIn: false
})
}
})
...
app.listen(process.env.PORT || port, () => {
console.log('Successfully Running server at ' + port + '.')
});
My Frontend:
import React, { useEffect, useState } from 'react'
import '../App.css'
import './HeroSection.css'
import { Link } from 'react-router-dom'
import Axios from 'axios'
function HeroSection() {
Axios.defaults.withCredentials = true
let username = "";
const [name, setName] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [isLoading, setLoading] = useState(true)
...
useEffect(() => {
Axios.get('https://xxx.herokuapp.com/login').then((response) => {
if (response.data.isLoggedIn) {
username = response.data.user[0].username;
}
setIsLoggedIn(response.data.isLoggedIn)
Axios.post('https://xxx.herokuapp.com/getLang', {
username: username,
}).then((response) => {
console.log(response.data);
})
Axios.post('https://xxx.herokuapp.com/getStatus', {
username: username,
}).then(response => {
setName(response.data[0].firstname + " " + response.data[0].lastname);
setLoading(false);
})
})
}, [])
if (!isLoggedIn || isLoading) {
return (
<div>
...
</div>
)
} else {
return (
<div>
...
</div>
)
}
}
export default HeroSection
By the way, I use ClearDB MySQL on Heroku and MySQL WorkBench for the database, which all works fine.
You could debug by doing something like:
const allowList = ["https://yyy.app/"];
// Your origin prop in cors({})
origin: function (origin, callback) {
// Log and check yourself if the origin actually matches what you've defined in the allowList array
console.log(origin);
if (allowList.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
I am trying to use express to create an endpoint so I can retrieve data from an api and return the json data in the body.
We are getting data from a rest api that returns an array of json data. What i would like is to use express router.get to display the json formatted on the front-end so i can then access the endpoint and get the data. Here is what i have so far:
"use strict";
const express = require("express");
const path = require("path");
const serverless = require("serverless-http");
const app = express();
const bodyParser = require("body-parser");
const fetch = require("node-fetch");
var async = require("express-async-await");
const router = express.Router();
router.get("/", async function(req, res, next) {
var requestOptions = {
method: "POST",
headers: {
Accept: "application/json",
Authorization:
"Basic *********",
"Access-Control-Allow-Origin": "*"
},
redirect: "follow"
};
async function getApi() {
try {
const response = fetch(
"http://www.reed.co.uk/api/1.0/search?employerId=*******",
requestOptions
)
.then(res => {
return res.json();
})
.then(json => {
return json;
});
return response;
} catch (error) {
console.log(error);
}
}
const ooIprocessData = async () => {
const data = await getApi();
const ooiResponseData = await data;
return ooiResponseData;
};
ooIprocessData();
res.end;
});
The below code returns the data in the node response when we access the router but it shows and I need to resolve it on the front-end.
Can anyone point me in the right place for this to work?
Thanks,
You have some unnessecary steps here. Also you could move your function outside of your router function.
"use strict";
const express = require("express");
const path = require("path");
const serverless = require("serverless-http");
const app = express();
const bodyParser = require("body-parser");
const fetch = require("node-fetch");
var async = require("express-async-await");
const router = express.Router();
router.get("/", async function(req, res) {
var requestOptions = {
method: "POST",
headers: {
Accept: "application/json",
Authorization: "Basic *********",
"Access-Control-Allow-Origin": "*"
},
redirect: "follow"
};
try {
let result = await getApi(requestOptions);
res.status(200).json(result);
} catch (err) {
return res.status(500).json(err);
}
});
function getApi(requestOptions) {
return fetch(
"http://www.reed.co.uk/api/1.0/search?employerId=*******",
requestOptions
).then(res => {
return res.json();
});
}
Your problem was also that getApi returns an promise that resolves to undefined because you try to return outside of the promise chain
Use async / await where its useful. Not where it could maybe work
Response has a function called json to send back JSON data. You can also optionally use Response.prototype.status to provide metadata about whether it worked or not. For example,
res.status(200).json({status: 1, msg: 'Fetched successfully'});
I'm using vue, vue-router for my client-side and express, morgan for my server side (MEVN app)
So, at the client i'm setting cookies by using vue-cookies
this.$cookies.set('Login', this.login, new Date(Date.now() + 86400 * 5 * 1000))
this.$cookies.set('Password', this.password, new Date(Date.now() + 86400 * 5 * 1000))
And at the server side i'm using cookieParser
So, at app.js i have such a code
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const config = require('./config/config');
const db = require('./controllers/DB');
const mCLogs = require('./modelControllers/Logs');
const mCLogin = require('./modelControllers/Login');
const app = express();
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(cors());
app.use(cookieParser()); /*cookie parser*/
And, at the file ./modelControllers/Login i have such a code for a GET request
exports.checkLoginSession = async (req, res, next) => {
/*its not all of the code*/
var loginHash = req.cookies['Login'];
console.log(loginHash)
if(loginHash == undefined) {
res.send({
logged: false,
description: "err mes"
});
} else {
res.send({
logged: true,
description: "mes"
});
}
}
and the problem is that the var loginHash = req.cookies['Login']; always return undefined, even when i have "Login" cookie
Addition:
How i call this method:
Client-side and using axios
mounted () {
this.getLoginData()
},
methods: {
async getLoginData () {
const response = await LoginHandler.checkUserLoginSession()
if (response.data.logged === true) {
this.$router.push('/')
} else {
this.errorMessage = response.data.description
}
}
}
LoginHandler.js(client side)
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login')
}
}
Server-side /login link in app.js
app.get('/login', mCLogin.checkLoginSession);
app.post('/login', mCLogin.checkUserData);
ADDITION:
It doesnt work when i use such a code with axios API:
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login')
}
}
So, when i call checkUserLoginSession app.get('/login') return cookie value undefined, but, if i open link in browser (serverside) localhost:3000/login it's returning correct value
Addition: checkUserData
exports.checkUserData = async (req, res) => {
try {
let login = req.body.login;
let password = req.body.password;
const user = await db.users.findOne({
where: {
Login: login,
Password: password
}
});
if(user == null)
{
res.send({
logged: false,
description: "Пользователь не найден."
});
return;
}
if(user.dataValues.Login == login && user.dataValues.Password == password)
{
res.send({
logged: true,
description: "Авторизация произошла успешно. Сейчас Вас перенаправит!"
});
return;
}
}
catch(ex) {
res.send({
logged: false,
description: "Произошла ошибка на стороне сервера."
});
console.log(ex);
return;
}
}
If i add withCredentials: true to axios.create, server return cookie value, but i've this errors on console line
Access to XMLHttpRequest at 'http://localhost:3000/login' from origin 'http://localhost:8080' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Ok guys, i solve my issue.
So, the answer is.
Change LoginHandler code:
From:
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login')
}
}
To:
import api from '#/services/api'
export default {
checkUserLoginSession () {
return api().get('/login', {withCredentials: true})
}
}
Change app.js
From:
app.use(cors());
To:
app.use(cors({ credentials: true, origin: "http://localhost:8080" }));
Change method checkLoginSession
To:
exports.checkLoginSession = (req, res, next) => {
const { Login, Password } = req.cookies;
//Where Login, Password ... is your cookie name
//console.log(Login)
if(Login == undefined) {
res.send({
logged: false,
description: "Нет сохранённых хешей для авторизации!"
});
} else {
res.send({
logged: true,
description: "Авторизован."
});
}
}
P.S Thanks to all, who tried to help me
Following the example from OAuth2WebServer from google I'm trying to set up an authentication flow from an express app using the HTTP/REST method they have but with every request I am returned with an error
I went through Google OAuth “invalid_grant” nightmare — and how to fix it but unfortunately it did not help.
{
error: "unsupported_grant_type",
error_description: "Invalid grant_type: "
}
This is a shortened version of the error I am receiving. If you need to see more of the error let me know and I can post it.
Server
const express = require('express');
const axios = require('axios');
const { web } = require('./src/client_id.json');
const app = express();
const { client_id, client_secret } = web;
let count = 0;
app.use(express.json());
/*************************
** REDIRECT USER TO GOOGLE AUTH **
*************************/
app.get('/', (req, res) => {
const redirect_uri = 'http://localhost:5000/auth';
const scope = 'https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly';
const access_type = 'offline';
res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?scope=${ scope }&access_type=${ access_type }&redirect_uri=${ redirect_uri }&response_type=code&client_id=${ client_id }`);
});
/*************************
** ON AUTH WE EXCHANGE ACCESS TOKEN FOR REFRESH TOKEN **
*************************/
app.get('/auth', (req, res) => {
count++;
if (count >= 2) {
return res.redirect('http://localhost:3000');
}
const { code } = req.query;
const redirect_uri = 'http://localhost:5000/auth';
const grant_type = 'authorization_code';
axios.post('https://www.googleapis.com/oauth2/v4/token', {
code,
client_id,
client_secret,
redirect_uri,
grant_type
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(data => {
console.log(data)
res.redirect('http://localhost:3000');
})
// ALWAYS HITS THE CATCH "Invalid grant_type"
.catch(err => {
console.log(err);
console.log('ERROR')
});
});
app.listen(5000, console.log('Server listening on port 5000'));