Axios not sending cookie data, even with withCredentials:true - javascript

Trying to make requests and send cookies along with it using React and Express. The requests/responses are working fine. But the cookies are not sent. On the client-side:
import axios from 'axios'
let endPoint = 'http://192.168.1.135:80'
axios.defaults.withCredentials = true;
export async function SubmitPost(username, title, body, time){
let res = await axios.post(`${endPoint}/posts/create`, {username: username, title: title, body: body, time: time})
return res.data
}
The server-side route:
router.post('/create', authenticator.authenticate, async (req, res) => {
let post = new Post({
title: req.body.title,
body: req.body.body,
time: req.body.time,
username: req.body.username
})
post.save().then((doc, err) => {
if (err) {
return res.send(Response('failure', 'Error occured while posting', doc))
}
res.send(Response('success', 'Posted Successfully'))
})
})
And the middleware authenticator:
module.exports.authenticate = (req, res, next) => {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (token == null) {
console.log('No authentication token found')
return res.sendStatus(401)
}
jtoken.verify(token, process.env.SECRET, (err, user) => {
if (err) {
console.log(err)
return res.sendStatus(403)
}
req.user = user
next()
})
}
It returns a 401 with a Token Not Found. I have enabled CORS on express as well as cookie-parser
app.use(cookieparser())
app.use(cors({origin: 'http://localhost:3000', credentials: true}))
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use('/auth', require('./routes/authentication_route'))
app.use('/posts', require('./routes/post_route'))

Related

Why am I getting 401 unauthorised when making calls from client to my API

I created simple react + express app for fetching data from Strava.
Authorization is done by passport.js and there aren't any problems when I run it locally (on separate ports client/server: 3000/8080 or using only server to provide production build) although when deployed the app on render.com I'm getting 401 unauthorised when making calls to my API.
I have two environments:
(client) static site for react app: https://statz.onrender.com
(server) web service for my express API: https://statz-api.onrender.com
For instance - to get logged athlete data client makes request to the server and then server is requesting that data from Strava API and returns the data to the client.
For now I have only two endpoints and both throw error 401 when called from client side.
Authentication is done server side and at the end the user is redirected back to the client. After all every request from client to API throws 401 but when calling endpoint directly from the browser eg: (https://statz-api.onrender.com/api/athlete) I'm getting complete data. Demo: https://share.getcloudapp.com/NQu4J7YL
Probably it's worth to mention there is no cookie in the request headers when calling from client.
I had some CORS errors first but probably I sorted them out since there is nothing about it on the console.
I would be grateful if somebody could look at this and show me what I'm still missing in my configs.
Link to github: https://github.com/PatrykJamroz/StravaStats/tree/deploy-1
server:
const express = require("express");
const passport = require("passport");
const cors = require("cors");
const logger = require("morgan");
const StravaStrategy = require("passport-strava-oauth2").Strategy;
const cookieParser = require("cookie-parser");
const bodyParser = require("body-parser");
const cookieSession = require("cookie-session");
const app = express();
app.use(
cors({
credentials: true,
origin: "https://statz.onrender.com",
exposedHeaders: ["Set-Cookie"],
})
);
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Credentials", true);
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
require("dotenv").config();
const fetch = (...args) =>
import("node-fetch").then(({ default: _fetch }) => _fetch(...args));
const STRAVA_CLIENT_ID = process.env.STRAVA_CLIENT_ID;
const STRAVA_CLIENT_SECRET = process.env.STRAVA_CLIENT_SECRET;
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use(
new StravaStrategy(
{
clientID: STRAVA_CLIENT_ID,
clientSecret: STRAVA_CLIENT_SECRET,
callbackURL: "/auth/strava/callback",
},
function (accessToken, refreshToken, profile, done) {
process.nextTick(function () {
return done(null, profile);
});
}
)
);
app.use(logger("combined"));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.methodOverride());
app.use(cookieSession({ secret: "keyboard cat" }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
const SCOPES = "read,activity:read_all,read_all";
app.get(
"/auth/strava",
passport.authenticate("strava", { scope: SCOPES }),
function (req, res) {
// The request will be redirected to Strava for authentication, so this
// function will not be called.
}
);
app.get(
"/auth/strava/callback",
passport.authenticate("strava", {
scope: SCOPES,
failureRedirect: "/login",
}),
function (req, res) {
res.redirect("https://statz.onrender.com");
}
);
app.get("/api/athlete", async (req, res) => {
fetch(
`https://www.strava.com/api/v3/athlete?access_token=${
req.user?.token ?? ""
}`
)
.then((response) => {
if (response.ok) {
return response.json();
}
res.status(response.status).json({ error: response.statusText });
throw new Error(`${response.status} - ${response.statusText}`);
})
.then((data) => res.json(data))
.catch((error) => console.error({ error }));
});
app.get("/api/activities", async function (req, res) {
if (!req.user) {
res.json({ error: "Not authenticated" });
}
let page = 1;
let activities = [];
while (true) {
const activitiesPromise = await fetch(
`https://www.strava.com/api/v3/athlete/activities?per_page=30&page=${page}&access_token=${req.user.token}`
);
const activitiesData = await activitiesPromise.json();
page += 1;
activities = [...activities, ...activitiesData];
logger({ page });
if (activitiesData.length < 30) {
return res.json(activities);
}
}
});
app.get("/api/ping", function (req, res) {
res.send("pong");
});
const listener = app.listen(process.env.PORT || 8080, () => {
console.log(`Your app is listening on port ${listener.address().port}`);
});
Client API calls:
import axios from 'axios';
import { Activity, Athlete } from '../models/Strava';
const BASE_URL = 'https://statz-api.onrender.com';
export const getAthlete = async (): Promise<Athlete> => {
return axios.get(`${BASE_URL}/api/athlete`, { withCredentials: true }).then(({ data }) => data);
};
export const getActivities = async (): Promise<Activity[]> => {
return axios
.get(`${BASE_URL}/api/activities`, { withCredentials: true })
.then(({ data }) => data);
};
Authorization component:
export function Authorization() {
const login = () => {
window.open('https://statz-api.onrender.com/auth/strava', '_self');
};
return (
<Button onClick={login} variant="outlined">
<img alt="authorize" src="/btn_strava_auth.png" />
</Button>
);
}

`req.session.user` is `undefined` when using `axios` and `express session`

I am trying to use express session with react/express frontend and backend. For the express server, I saved the user information in express session in success callback, then I set the credential in the express option and cors to true. However, when I use axios post request from the React frontend with withCredential set to true, req.session.user still shows up as undefined. Does anyone know how to resolve this?
I tried to use middleware, but they were not helpful.
In server.mjs
import express from 'express';
import session from 'express-session';
import mongoose from 'mongoose';
import cors from 'cors';
import User from './db.mjs';
import path from 'path';
import url from 'url';
import bcrypt from 'bcryptjs';
import bodyParser from 'body-parser';
import router from './routes/user.routes.mjs';
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session({
secret: 'keyboard cat',
cookie: {
maxAge: 600000,
secure: true
},
resave: false,
saveUninitialized: true,
}))
app.use(
cors({
allowedHeaders: ["authorization", "Content-Type"], // you can change the headers
exposedHeaders: ["authorization", 'set-cookie'], // you can change the headers
origin: "*",
credentials: true,
origin: true,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
preflightContinue: false
})
);
app.options({
origin: true,
credentials: true,
})
app.use('/public', express.static('public'));
app.use('/api', router)
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
// make {{user}} variable available for all paths
app.use((req, res, next) => {
res.locals.user = req.session.user;
next();
});
// logging
app.use((req, res, next) => {
console.log(req.method, req.path, req.body);
next();
});
const login = (email, password, successCallback, errorCallback) => {
// TODO: implement login
const user = User.findOne({'email': email}, function (err, user) {
console.log(err,user)
if (err) {
console.log(err);
errorCallback('USER SEARCH FAILED')
}
else {
if (user === null) {
errorCallback('USER NOT FOUND');
}
else {
bcrypt.compare(password, user.password, (err, passwordMatch) => {
if (err) {
console.log(err);
errorCallback("PASSWORD CHECK FAILED")
}
else {
if (passwordMatch) {
successCallback(user);
}
else {
errorCallback("PASSWORDS DO NOT MATCH");
}
}
});
}
}
});
};
const startAuthenticatedSession = (req, user, cb) => {
// TODO: implement startAuthenticatedSession
console.log('starting new authenticated session', user);
req.session.regenerate(function(err) {
if (!err) {
// set a property on req.session that represents the user
req.session.user = user;
cb(err);
} else {
// call callback with error
cb(err);
}
})
};
const endAuthenticatedSession = (req, cb) => {
// TODO: implement endAuthenticatedSession
req.session.destroy((err) => { cb(err); });
};
const signup = (email, password, errorCallback, successCallback) => {
// TODO: implement register
if (password.length < 8) {
errorCallback('PASSWORD TOO SHORT');
}
User.findOne({'email': email}, function(err, result) {
console.log(result);
if (result === null) {
const hash = bcrypt.hash(password, 10, function(err, hash) {
// do more stuff here!
if (err) {
console.log(err);
errorCallback({message: 'PASSWORD HASH FAILED'})
}
const newUser = new User({
email: email,
password: hash
});
newUser.save(function(err, user, count) {
if (err) {
errorCallback('DOCUMENT SAVE ERROR')
}
successCallback(user);
console.log(req.session.user);
});
});
}
else {
errorCallback('EMAIL ALREADY EXISTS');
}
})
User.findOne({'email': email});
};
app.get("/api", (req, res) => {
res.json({"user": ["userOne", "userTwo", "userThree"]})
})
app.post("/api/login/", (req, res) => {
const email = req.body.email;
const password = req.body.password;
console.log(req.body)// assumes that User was registered in `./db.mjs`
function success(newUser) {
startAuthenticatedSession(req, newUser, (err) => {
if (!err) {
req.session.user = newUser;
res.send({key:'success'});
} else {
res.render('error', {message: 'err authing???'});
}
});
}
login(email, password, success, (e)=>console.log(e));
})
In react frontend component
import '../../App.css';
import React from 'react';
import axios from 'axios';
import icon from '../../img/upload.svg';
axios.defaults.withCredentials = true;
function handleFile(files) {
alert("Number of files: " + files.length);
console.log(files);
const profileImg = 'profileImg';
console.log(files[0].name);
const formData = new FormData();
formData.append(profileImg, files[0]);
axios.post("http://localhost:8000/api/user-profile", formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
withCredentials: true
}).then(res => {
console.log(res);
});
}
console.log for undefined in post request
router.post('/user-profile', upload.single('profileImg'), (req, res, next) => {
const url = req.protocol + '://' + req.get('host')
console.log(req.file);
console.log(req.session.user);
Edit: the console log for req.session:
Session {
cookie: {
path: '/',
_expires: 2022-12-05T04:56:43.020Z,
originalMaxAge: 600000,
httpOnly: true,
secure: true
}
}

Passport authentication missing credentials in browser

I am learning how to implement user authentication using passport.js. I have a basic passport "local" strategy set up on the server side and so far just a single POST route to log a user in. This all works exactly as intended when troubleshooting with insomnia but when I make the same request from the browser I get a message missing credentials. This message is coming from console.log(info) in controllers/auth.js.
I have tried including credentials in the fetch request as seen below but I must be missing something else or including them incorrectly. I have also changed the variable names from 'email' to 'username' since I read that was the default for passport.js.
From what I can tell in Chrome dev tools, the request body is formatted correctly and I am hitting the proper endpoint.
controllers/auth.js
const express = require("express");
const router = express.Router();
const passport = require("passport");
router.post("/register_login", (req, res, next) => {
passport.authenticate("local", function(err, user, info) {
console.log(info)
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function(err) {
if (err) {
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
});
module.exports = router;
passport/setup.js
const bcrypt = require('bcrypt');
const { User } = require('../models');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
// Local Strategy
passport.use(new LocalStrategy((username, password, done) => {
// Match User
User.findOne({ email: username })
.then(user => {
// Create new User
if (!user) {
return done(null, false, { message: 'No user found!!'})
// Return other user
} else {
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Wrong password' });
}
});
}
})
.catch(err => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
client side fetch
const handleLogin = async (evt) => {
evt.preventDefault();
const response = await fetch('/auth/register_login', {
method: 'POST',
credentials: 'include',
withCredentials: true,
body: JSON.stringify({
"username": "test#email.com",
"password": "password"
})
})
return response;
};
Oof, that was a simple one...
I was reading the "Content-Type" in Chrome dev tools however I was reading the "Response Headers" thinking they were the "Request Headers". The issue is that I was sending text instead of json.
Changing the client side fetch to the snippet below resolved the issue.
client side fetch
const handleLogin = async (evt) => {
evt.preventDefault();
const response = await fetch('/auth/register_login', {
method: 'POST',
credentials: 'include',
withCredentials: true,
headers: {
'Content-Type': 'application/json' // <--add this
body: JSON.stringify({
"username": "test#email.com",
"password": "password"
})
})
return response;
};

req.headers['authorization'] is undefined in Nodejs JWT(JSON WEB TOKEN)

Here is the code for JWT:
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.json());
const user = [
{
name: "Rohan",
id: 1,
},
{
name: "Sophie",
id: 2,
},
{
name: "Charlie",
id: 3,
},
];
app.get("/", (req, res) => {
res.send("Welcome to Homepage");
});
app.get("/id", verifyToken, (req, res) => {
res.json(user.filter((id) => user.name === req.user.name));
});
function verifyToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(" ")[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, "secretKey", (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.post("/login", (req, res) => {
const username = req.body.username;
const user = { name: username };
jwt.sign(user, "secretKey", (err, token) => {
res.json({ token: token });
});
});
app.listen(4000, () => {
console.log("Server is listening on port: 4000");
});
The req.headers['authorization'] is returning undefined when console.log(The req.headers['authorization'])
This code for JWT always return Status 401 (Unauthorized) when the request is sent in the format Authorization: Bearer "token" ,
Please help !!
Do you use the Postman for test?
add 'authorization' key in headers section on the postman, like picture:
and not need 'authHeader.split(" ")1;' , please change your code like this:
const token = req.headers["authorization"];
// const token = authHeader && authHeader.split(" ")[1];
console.log(token)

Axios data is breaking the request

I have an express API and a ReactJs front-end. I try to make a POST call from my front-end directly to the local API.
For this I'm using axios.
The request is working fine when I set the parameters directly inside the query string but is always getting on timeout if I try to add the parameters through the data attribute of the axios.post() method.
Working
axios.post(`http://localhost:5001/site/authenticate?username=demo&password=demo`)
Not working
const payload = {
"username":"mh",
"password":"mh"
}
axios.post(`http://localhost:5001/site/authenticate`, payload)
My express server:
const express = require('express');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
var cors = require('cors');
const app = express();
const port = process.env.API_PORT || 5001;
app.use(cors());
app.set('secret', process.env.API_SECRET);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(morgan('dev'));
app.use((req, res, next) => {
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', () => {
req.rawBody = data;
next();
});
});
// Allow CORS
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// SITE ROUTES -------------------
const siteRoutes = express.Router();
siteRoutes.post('/authenticate', function(req, res) {
console.log('auth');
getDocument(usersBucket, req.query.username)
.then((doc) => {
console.log("Authentification... TODO");
// return the information including token as JSON
res.json({
success: true,
status: 200,
token: token
});
})
.catch(() => {
res.status(401).json({ success: false, message: 'Authentification failed. User not found.' });
});
});
// route middleware to verify a token
siteRoutes.use(function(req, res, next) {
const token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('secret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.', status: 401 });
} else {
req.decoded = decoded;
next();
}
});
} else {
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
siteRoutes.get('/', function(req, res) {
res.json({ message: 'Welcome!' });
});
app.use('/site', siteRoutes);
app.listen(port, () => {
logger.log(`Express server listening on port ${port}`);
});
Any idea? Thanks.
Update
I replaced my route just to see if I got in or not (without worrying about parameters):
siteRoutes.post('/authenticate', function(req, res) {
console.log("go in");
res.json({
success: true,
status: 200,
});
});
But my console.log is not showing hen I use the payload (it is when I do not).
You should access the payload data via request.body, not the request.query:
// SITE ROUTES -------------------
const siteRoutes = express.Router();
siteRoutes.post('/authenticate', function(req, res) {
console.log('auth');
getDocument(usersBucket, req.body.username) // <------- HERE
.then((doc) => {
console.log("Authentification... TODO");
// return the information including token as JSON
res.json({
success: true,
status: 200,
token: token
});
})
.catch(() => {
res.status(401).json({ success: false, message: 'Authentification failed. User not found.' });
});
});
request.query are the parameters passed in the URL, like:
protocol://hostname:port/path/to.route?query_param_0=value_0&query_param_1=value_1
on your express endpoint request.query will be:
{
query_param_0: value_0,
query_param_1: value_1
}
while sending the payload, with the second argument in axios.post(url, payload):
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
on your express endpoint request.body will be:
{
firstName: 'Fred',
lastName: 'Flintstone'
}
when you use app.use(bodyParser.json()); (and you do).
You are using “getDocument(usersBucket, req.query.username)”
This means you express route is expecting username as a request param. That’s why it’s working when you use “?username=xx”
Instead try to get it from json body of request.
“req.body.username”
Also you should consider validating the request body or param as required.

Categories