The scenario:
I have a page in my app which users seem to be leaving open and returning to well after their session / CSFR have expired
When they try and submit data from that page and the session is rejected, I have a feature to open up a log-in modal
On submitting that, there's first a request to the server to issue a new CSRF token
On receiving that, it then uses that new token, and sends it with their user credentials to generate a new session
They can then continue with what they were doing before
The problem is that at step 4, the request fails due to an invalid CSRF token.
On the server side, the two routes look like this (using express, csurf):
const csrf = require("csurf");
const csrfProtection = csrf({ cookie: true });
const passport = require("passport");
//Issue new ajax csrf login token
app.get("/ajaxLoginToken", csrfProtection, function (req, res) {
const csrfToken = req.csrfToken();
res.json({ csrfToken });
console.log("New csrf token issued", csrfToken);
});
//AJAX login
app.post("/ajaxLogin", csrfProtection, function (req, res, next) {
passport.authenticate("local", async function (err, user, info) {
//This point is never reached due to csrf mismatch
On the browser, step 3 looks something like this:
async requestNewToken() {
let response = await fetch("/ajaxLoginToken", {
method: "GET",
});
let responseBody = await response.json();
//Update csrf token
document.querySelector('meta[name="csrf-token"]').setAttribute("content", responseBody.csrfToken);
document.cookie = `_csrf=${responseBody.csrfToken}`;
Then to send step 4, it adds the csrf token to the request before sending
async login({email, password}) {
let response = await fetch(request.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').getAttribute("content")
},
body: JSON.stringify({email, password}),
//this always fails due to csrf token
When I try debugging, the new token is being received on the log in request, but it's rejected. Any help would be amazing - I've been bashing my head at this for days.
Hard to say without a minimal running example, but I guess you didn't include the CSRF cookie. By calling csurf with csurf({ cookie: true }) you decided to maintain the CSRF Token on client side in form of a cookie, by default called _csrf. You already did it. This mechanisms is called Double Submit Cookie.
Up to now, you have to send the CSRF token twice with every request: As an Header or a request param, depends on how you configured it, see csurf - value for the defaults, AND in form of the cookie. As far as I can see, you never send the cookie in your AJAX request. You send the CSRF Token as Header X-CSRF-Token but you have to include the cookie as well.
Just a guess, but could it be possible that you have to configure your fetch call by using the credentials option? By default, fetch doesn't include cookies. You have to incude them by using credentials: "include". Be aware, you can use credentials: "include" only if the Access-Control-Allow-Origin header is configured, wildcards are not possible when using include.
To sum it up, I guess you never send the cookie in your AJAX call and you have to configure it.
Related
I am using JWT in my node.js app. Everything with token works fine. I can get a token when I have logged in.
Here's how I check user auth.:
const jwt = require('jsonwebtoken')
try{
const token = req.headers.authorization
const decoded = jwt.verify(token, 'secret')
req.userData = decoded
next()
}
catch(err){
return res.send("Auth error")
}
I can access protected routes if I change token value with that token I've got after log in.
But I want to save the token (on user's side???), and each time when the user tries to access protected routes (from frontend), send token as req.headers.authorization, so that the token can be verified, and the user can access the route.
So, how to save and later send the token, that has been generated after user's log in each time when protected routes are linked to?
Thank you.
(I am not using any javascript frontend frameworks)
If it's a token for authentication, you can use a httpOnly cookie to prevent XSS attacks, which is a risk with local storage.
To save a JWT in a cookie in express:
const accessToken = jwt.sign();
return res
.status(201)
.cookie("accessToken", accessToken, {
httpOnly: true,
maxAge: (1000*60*5), // 5m
secure: true,
signed: true
})
.end();
When getting from requests:
const { accessToken } = req.signedCookies;
Inside your app.js:
const express = require("express");
const cookieParser = require("cookie-parser");
const app = express();
app.use(cookieParser("secret"));
With httpOnly cookies your requests automatically send the cookie along with the request (only if the cookie belongs to the same domain). Just make sure your CORS and http client are properly configured to handle the cookie
Common approach to save it to a local storage. Just keep in mind local storage size and other limitations in different browsers.
I am creating login module.
User will enter Username and Password.
If user validate successfully then Server will return JWT token.
I will use the JWT token to validate the different API call in React js.
Now my concern is that I found some article regarding this then I found that We can use http only cookie. How can we implement httponly cookie method to store JWT ? Is it safe?
HttpOnly cookies are safe in that they are protected from browser access via the Document.cookie API, and therefore are protected from things like XSS attacks.
When your user is successfully validated, the server should generate a jwt token and return it as a cookie to your client like so:
return res.cookie('token', token, {
expires: new Date(Date.now() + expiration), // time until expiration
secure: false, // set to true if you're using https
httpOnly: true,
});
The cookie will be accessible via incoming http requests from your client. You can check the jwt value of the cookie with an authorizing middleware function to protect your API endpoints:
const verifyToken = async (req, res, next) => {
const token = req.cookies.token || '';
try {
if (!token) {
return res.status(401).json('You need to Login')
}
const decrypt = await jwt.verify(token, process.env.JWT_SECRET);
req.user = {
id: decrypt.id,
firstname: decrypt.firstname,
};
next();
} catch (err) {
return res.status(500).json(err.toString());
}
};
Reference for more details: https://dev.to/mr_cea/remaining-stateless-jwt-cookies-in-node-js-3lle
I have a simple express server setup like:
app.use(bodyParser.json());
app.use(cookieParser());
app.use(csurf({ cookie: true }));
// routes
app.use(Routes imported from another file);
The client is currently just a simple form in react. I am loading some initial data before the react app loads and the csrf cookie is being set there.
I have a simple function for parsing the csrf cookie client side. I'm proxying the express server in create-react-app so I can't just set a meta tag in the header.
const csrfToken = () => {
const cookies = decodeURIComponent(document.cookie).split(';');
const token = cookies.find(cookie => cookie.includes('_csrf'));
if (token) {
return token.split('=')[1]
}
}
I am using fetch to send along data and the token
const response = await fetch(url, {
credentials: 'include',
method: 'POST',
headers: {
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken()
},
body: JSON.stringify({ ...body })
});
I've tried commenting out the line that tells the app to use csurf and checking that everything is present on the request. I can verify that the cookie and the header are matching in every request I send. Everything seems correct, but I am still getting a 403 error so I must be missing something. I'm at a lost to what it could be and all I could find googling is other people setting up their apps very similarly.
You are reading the content of the _csrf cookie and sending it back inside X-CSRF-Token header. This will not work.
The csurf middleware running inside Express has been configured by this code: app.use(csurf({ cookie: true })); to generate the _csrf cookie and send it to the client. The middleware expects you to:
Generate the second piece of CSRF data on the server.
Attach the second piece of data to the response sent to a client. As a result, the response arrives to the client with both the _csrf cookie and the second piece of data attached.
Ensure the incoming request from the client has the same _csrf cookie and the second piece of data copied into one of the six predefined places/locations (such as 'X-CSRF-Token' header or another location).
See this answer for more details.
I'm trying to set up a React/Redux - NodeJs Express stack with Google OAuth authentication. My issue is a CORs error kicking back in the console. I've found some Stack Overflow questions that I feel were exactly my issue, but the solutions aren't producing any results. Specifically these two: CORS with google oauth and CORS/CORB issue with React/Node/Express and google OAuth.
So I've tried a variety of fixes that all seem to lead me back to the same error. Here's the most straight forward of them:
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200,
credentials: true
}
app.use(cors(corsOptions));
This is in the root of my API.js file. The console error I receive state:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fapi%2Foauth%2Fgoogle%2Freturn&scope=profile&client_id=PRIVATE_CLIENT_ID.apps.googleusercontent.com' (redirected from 'http://localhost:5000/api/oauth/google') from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So if I look at my network log in the dev tools, I look at my request to the API path and see what I expect to see:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: http://localhost:3000
So it seems to me that my issue isn't within my front to back communication. Which leads me to believe it's maybe an issue with the Passport token validation. Here are my simplified routes:
router.post('/oauth/google', passport.authenticate('googleVerification', {
scope: ['profile']
}), (req, res) => {
console.log('Passport has verified the oauth token...');
res.status(200)
});
And the callback route:
router.get('/oauth/google/return', (req, res) => {
console.log('google oauth return has been reached...')
res.status(200)
});
And lastly, the simplified strategy:
passport.use('googleVerification', new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: 'http://localhost:5000/api/oauth/google/return'
}, (accessToken, refreshToken, profile, cb) => {
console.log('Passport OAuth Strategy reached');
cb(null, profile)
}));
I know all these won't lead to anything functional, but I've just ripped out as much fluff as I can trying to get a handle on where the block in my authentication flow is. Just in case it may be helpful in narrowing this down, here is the action creator in Redux that logs the last step in the process before the errors start coming ('redux accepting token and passing to API:', token):
export const signIn = (token) => {
console.log('redux accepting token and passing to API:', token)
return async dispatch => {
const res = await Axios({
method: 'post',
url: `${API_ROOT}/api/oauth/google`,
withCredentials: true,
data: {
access_token: token
}
})
console.log('API has returned a response to redux:', res)
dispatch({
type: SIGN_IN,
payload: res
})
}
};
This never actually reaches the return and does not log the second console.log for the record.
That CORS is not related to making request to google because when you registered your app in console.developers.google.com it is already handled by google.
The issue is between CRA developer server and express api server. You are making request from localhost:3000 to localhost:5000. To fix this use proxy.
In the client side directory:
npm i http-proxy-middleware --save
Create setupProxy.js file in client/src. No need to import this anywhere. create-react-app will look for this directory
Add your proxies to this file:
module.exports = function(app) {
app.use(proxy("/auth/google", { target: "http://localhost:5000" }));
app.use(proxy("/api/**", { target: "http://localhost:5000" }));
};
We are saying that make a proxy and if anyone tries to visit the route /api or /auth/google on our react server, automatically forward the request on to localhost:5000.
Here is a link for more details:
https://create-react-app.dev/docs/proxying-api-requests-in-development/
by default password.js does not allow proxied requests.
passport.use('googleVerification', new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: 'http://localhost:5000/api/oauth/google/return',
proxy:true
}
One important thing here is, you should understand why proxy is used. As far as I understood from your code, from browser, you make request to express, and express will handle the authentication with password.js. After password.js runs through all authentication steps, it will create a cookie, stuffed it with the id, give it to express and express will send it to the browser. this is your app structure:
BROWSER ==> EXPRESS ==> GOOGLE-SERVER
Browsers automatically attaches the cookie to the evey request to server which issued the cookie. So browser knows which cookie belongs to which server, so when they make a new request to that server they attach it. But in your app structure, browser is not talking to GOOGLE-SERVER. If you did not use proxy, you would get the cookie from GOOGLE-SERVER through express, but since you are not making request to GOOGLE-SERVER, cookie would not be used, it wont be automatically attached. that is the point of using cookies, browsers automatically attaches the cookie. BY setting up proxy, now browser is not aware of GOOGLE-SERVER. as far as it knows, it is making request to express server. so every time browser make request to express with the same port, it attaches the cookie. i hope this part is clear.
Now react is communicating only with express-server.
BROWSER ==> EXPRESS
since react and exress are not on the same port, you would get cors error.
there are 2 solutions. 1 is using the cors package.
its setup is very easy
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors()) // use this before route handlers
second solution is manually setting up a middleware before route handlers
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"OPTIONS, GET, POST, PUT, PATCH, DELETE"
);
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
next(); // dont forget this
});
You can't make an axios call to the /oauth/google route!
Here's my solution... Code is slightly different but you will get the concept.
// step 1:
// onClick handler function of the button should use window.open instead
// of axios or fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
Explanation....
step 1 will load your servers auth url on your browser and make the auth request.
step 2 then reload the client url on the browser when the authentication is
complete.
step 3 makes an api endpoint available to collect user info to update the react state
step 4 makes a call to the endpoint, fetches data and updates the users state.
I'm building an app with Node.js + Express, and I'm trying to use JSON Web Tokens for authentication. Right now I am at the point where once a valid username/password is entered, the server responds by sending the client a JWT.
This is where I get lost.
How do I send that token along with further requests to the server?
How can I send it as a header?
How do I send that token along with further requests to the server?
You can append in your req URL as query parameter.
Eg:
http://localhost:8080/api/users?token=tokenValue
You can save it in cookies and when you req a URL, it will fetch up this cookie containing your token. Use document.cookie to save token in your cookie
How can I send it as a header?
Using JQuery
$.ajax({
type:"POST",
beforeSend: function (request)
{
request.setRequestHeader("Authority", authorizationToken);
},
url: "entities",
data: "",
success: function(msg) {
}
});
At Server Side, you can do:
var token = req.body.token || req.query.token || req.headers['x-access-token'];
For Cookie Parsing, you can use: Cookie-Parser
var app = express()
app.use(cookieParser())
app.get('/', function(req, res) {
console.log("Cookies: ", req.cookies)
})
Further Reading:
https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens
client can set the access token either as header or query parameter or in request body. Following is a way to send via header:
$.ajax({
url: 'foo/bar',
headers: { 'x-access-token': 'some value' },
data: {}
}).done(function(result){
//do something
});
Best practice is to save the access-token in browser local storage rather than in cookie. Once you obtain the token once logged it.
Server, best way to include a authentication middleware above all secured routes, where token is required.
auth.middleware:
'use strict';
module.exports = function(req,res,next){
const jwt = require('jsonwebtoken');
const config = require('../config/config');
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, config.secret, function(err, decoded) {
if (err) {
return res.status(401).json({ success: false, message: 'Failed to authenticate token.' });
} else {
// if everything is good, save to request for use in other routes
req.decoded = decoded;
next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
};
routes:
//no token required
app.post('/signup',users.create);
app.post('/login',users.authenticate);
const auth = require('../middleware/auth.middleware');
//token required for below routes
app.use(auth);
app.get('/info',index.getInfo);
first you need to set the json token in client by using http cookie (res.cookie("token","yourtoken")) or using session
when user sends a request you need to send the token to server.you can read cookie by using req.cookie.token and verify it in middleware or use session