Passport.js authentication inconsistent between Express routes - node.js

I am trying to use passport authentication with a local strategy provided by passport-local-mongoose to authenticate a user. If the user is authenticated, then he is allowed to view the /secret route, else he gets a bad request message (provided by passport).
The weird part is that the authentication works for the login POST route, which successfully redirects to the /secret page upon correct credentials. But on redirection, the user gets a bad request which means that authentication fails at the /secret route. This is very confusing as the user could only be redirected to /secret if he was successfully authenticated upon login, but upon redirection to /secret, authentication fails and a bad request error is sent.
User Schema:
const mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
const userSchema = new mongoose.Schema({
username: String,
password: String
});
userSchema.plugin(passportLocalMongoose);
module.exports = new mongoose.model("User", userSchema);
Server Configuration:
const express = require("express"),
mongoose = require("mongoose"),
passport = require("passport"),
bodyParser = require("body-parser"),
LocalStrategy = require("passport-local"),
expressSession = require("express-session");
const User = require("./models/user");
const app = express();
app.set("view engine", "ejs");
app.use(
expressSession({
secret: "Lorem Ipsum",
resave: false,
saveUninitialized: false
})
);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());
app.use(passport.session());
mongoose.connect("mongodb://localhost:27017/auth-test", {
useNewUrlParser: true,
useUnifiedTopology: true
});
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
Login and Secret Routes:
app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/secret",
failureRedirect: "/register"
})
);
app.get("/secret", passport.authenticate("local"), (req, res) => {
res.render("secret");
});
Login form in case it is needed, used on the /login GET route:
<form action="/login" method="POST">
<input type="text" placeholder="Username" name="username" autocomplete="off" />
<input type="password" placeholder="Password" name="password" autocomplete="off" />
<button type="submit">Submit</button>
</form>

Checking passport-local code, it seems that Authenticate() is used to verify the user's credentials (username, password), so basically you'll only need to use it in /login route.
To verify if the user is authorized to access a protected route, you can use req.isAuthenticated() instead.
Example:
app.get("/secret", (req, res) => {
if (!req.isAuthenticated()) {
return res.sendStatus(401);
}
res.render("secret");
});

Related

Possible to log in specific user with Passport Strava strategy without log in screen?

I want to have an online map which publicly loads and shows all my activities from my Strava account.
I have found a web app on GitHub which does what I want but the user has to log in with the Strava log in screen before he than can see his own activities: https://github.com/nsynes/ActivityMap
It seems it uses Passport Strava to authenticate with Strava: https://www.passportjs.org/packages/passport-strava-oauth2/
Is it possible to adjust the script so it always logs in automatically my account and than shows my activities publicly to everyone who visits the map?
The full script is here: https://github.com/nsynes/ActivityMap/blob/master/activity-map-app.js
My STRAVA_CLIENT_ID and STRAVA_CLIENT_SECRET are from my Strava account and saved in a .env file.
const express = require('express')
const passport = require('passport')
const util = require('util')
const StravaStrategy = require('passport-strava-oauth2').Strategy
const dotenv = require('dotenv');
const path = require('path');
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser');
const session = require('express-session');
var strava = require('strava-v3');
const decode = require('geojson-polyline').decode
const geodist = require('geodist');
dotenv.config();
const port = process.env.PORT || 3000
const app = express();
// configure Express
//app.use(express.logger());
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
//app.use(express.methodOverride());
app.use(session({
secret: 'monkey tennis',
resave: true,
saveUninitialized: true,
maxAge: 1800 * 1000
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/css', express.static(path.join(__dirname, 'css')));
app.use('/fontawesome', express.static(path.join(__dirname, 'fontawesome')));
app.use('/js', express.static(path.join(__dirname, 'js')));
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Passport session setup.
passport.serializeUser(function(user, done) { done(null, user) });
passport.deserializeUser(function(obj, done) {done(null, obj) });
passport.use(new StravaStrategy({
clientID: process.env.STRAVA_CLIENT_ID,
clientSecret: process.env.STRAVA_CLIENT_SECRET,
callbackURL: "/auth/strava/callback"
},
function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Strava profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Strava account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}
));
app.get('/', ensureAuthenticated, function(req, res){
pagePath = path.join(__dirname, '/index.html');
res.sendFile(pagePath);
});
app.get('/userPhoto', ensureAuthenticated, function(req, res){
if ( req.user ) {
res.json({ 'photo': req.user.photos[req.user.photos.length-1].value });
} else {
res.sendStatus(404);
}
});
// Use passport.authenticate() as route middleware to authenticate the
// request. Redirect user to strava, then strava will redirect user back to
// this application at /auth/strava/callback
app.get('/auth/strava',
passport.authenticate('strava', { scope: ['activity:read'], approval_prompt: ['force'] }),
function(req, res){
// The request will be redirected to Strava for authentication, so this
// function will not be called.
});
// GET /auth/strava/callback
// Use passport.authenticate() as route middleware to authenticate the
// request. If authentication fails, the user will be redirected back to the
// login page. Otherwise, the primary route function function will be called,
// which, in this example, will redirect the user to the home page.
app.get('/auth/strava/callback',
passport.authenticate('strava', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});
app.get('/logout', function(req, res){
req.logout();
res.cookie("connect.sid", "", { expires: new Date() });
res.render('login', { user: req.user });
});
app.get('/login', function(req, res){
res.render('login', { user: req.user });
});

Returning a JSON response for passport-local when password or name is not correct

I am working on passport and passport-local for authentication and the problem I have now is that I want to return a JSON response to the frontend when the password or username is wrong but i keep getting the below from passport
my localstrategy
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const User = require("../models/user");
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
I am calling it on my route this way
router.post(
"/login",passport.authenticate("local", {
failureMessage: true,}),login);

Axios then callback not working on successful Post request

Been working on figuring out user login/register with react and why my axios is not calling .then on successful post request to my api.
Server.js
//Node packages
const path = require('path');
//Required NPM Packages
const express = require('express'),
app = express(),
session = require('express-session'),
cors = require('cors'),
bodyParser = require('body-parser'),
mongoose = require('mongoose'),
MongoStore = require('connect-mongo')(session),
methodOverride = require('method-override'),
passport = require('passport'),
LocalStrategy = require('passport-local');
//MongoDB models.
const Product = require('./models/Product');
const User = require('./models/User');
//Routes.
const indexRoute = require('./routes/index');
const demoRoute = require('./routes/demos');
const blogRoutes = require('./routes/blogs');
const userRoutes = require('./routes/users');
//Port.
const PORT = 5000;
const DATABASE_URI = require('./config/database');
const mongoOptions = { useNewUrlParser:true, useUnifiedTopology:true};
//Connect to mongoDB.
mongoose.connect(DATABASE_URI, mongoOptions);
const sessionOptions = {
secret: 'somesecretword',
resave: true,
saveUninitialized: true,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}
//set session options
app.use(session(sessionOptions));
//Setup body-parser.
app.use(express.json());
app.use(bodyParser.urlencoded({extended:true}));
//Allow express/node to accept Cross-origin resource sharing.
app.use(cors());
app.use(express.static(path.join(__dirname, '..','client','build')));
//Setup method override.
app.use(methodOverride("_method"));
//Congifure passport.
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
app.use(function(req,res,next){
res.locals.currentUser = req.user;
next();
})
//register API routes to express.
app.use('/', indexRoute);
app.use('/demos', demoRoute);
app.use('/blogs', blogRoutes);
app.use('/users', userRoutes);
// //Register React routes to express
app.use('about', express.static(path.join(__dirname, '..','client','build')));
app.get('*', (req,res)=> {
res.sendFile(path.join(__dirname,'..','client','build','index.html'));
})
//listen to established port.
app.listen(PORT, () => {
console.log(`The server has started on port ${PORT}!`);
});
module.exports = app;
login route
router.post('/login', passport.authenticate('local'), (req, res) => {
console.log('success');
res.json({authenticated:true});
});
React front-end function
async function handleRegister(evt){
//Prevent default form redirect.
evt.preventDefault();
//Create a new user objec to pass into axios
const user = {
username: username,
password: password
}
//Send axios post request to nodeJS API.
await axios.post("http://*******/users/register", user)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
//Push react history back to index page.
}
Right now I'm using Passport.js with passport.local.mongoose Strategy and connect-mongo. When I go ahead and post to the login route with the correct users information, the callback on the back end returns success. As per the passport.authenticate method if auth is a success then we console.log and send res.json.
I've tried to use res.send, res.json, res.sendStatus but none of them seem to work. Am I missing some sort of setup with passport? As far as the documentation goes for passport-local-mongoose I shouldn't have to establish a config.
All I want to happen is that when I login I send a redirect to react and push the response route to react-router's history object via history.push(routeUrl);
I found out the problem. Was just an error on my part. Was working with the register function and not the login in function. The issue wasn't that I wasn't getting a response... Probably a queue for a break.

Invalid csrf token issue express nodejs

I am trying to implement csrf tokens for the first time and i am running into issues. I've been working at it for a few hours and haven't been able to solve it. Below is the error I am getting:
ForbiddenError: invalid csrf token
app.js
const express = require('express')
const app = express()
const router = require('./router')
const cookieParser = require('cookie-parser')
const session = require('express-session')
const flash = require('connect-flash')
const dotenv = require('dotenv')
const csrf = require('csurf')
dotenv.config()
app.use(express.urlencoded({extended: false}))
app.use(express.json())
app.use(express.static('public'))
app.use(cookieParser('secret'))
app.use(session({
secret: 'secret',
cookie: {maxAge: null},
resave: false,
saveUninitialized: false
}))
app.use(flash())
app.set('views', 'views')
app.set('view engine', 'ejs')
app.use(csrf())
app.use(function(req, res, next) {
res.locals.csrfToken = req.csrfToken()
next()
})
app.use('/', router)
app.use(function (req, res, next) {
res.status(404).render('404')
})
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).render('404')
})
app.listen(process.env.PORT)
router.js
const express = require('express')
const multer = require('multer')
const multerConfigOpts = require('./multer.config')
const router = express.Router()
const userController = require('./controllers/userController')
const csrf = require('csurf')
var csrfProtection = csrf({ cookie: true })
// set multer configuration options
const upload = multer(multerConfigOpts)
router.get('/', userController.home)
router.get('/about', userController.about)
router.get('/employer', userController.employer)
router.get('/jobSeeker', userController.jobSeeker)
router.get('/ourProcess', userController.process)
router.get('/contact', userController.contactUs)
// Talent Request Post related routes
router.post('/talentrequest',upload.none() ,userController.requestTalent)
// Job Request Post related routs
router.post('/jobrequest', csrfProtection, upload.single('resume'), userController.requestJob)
module.exports = router
Example of my form:
<form action="/jobrequest" method="POST" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="btn--form-submit">Submit</button>
</div>
</form>
There are more data fields, I just didn't want to bloat the question with unnecessary code. I've been reading that others are having similar issues when using multipart in the form, but I can't seem to figure it out.
I know that my token is being generated inside the form but I'm not sure if its being passed through properly. Any help or pointers would be appreciated. Thank you
So I was able to find a work around solution by adding the following to my form and removing the input hidden field from my form
form action="/talentrequest/*?_csrf=<%= csrfToken %>*" method="POST" enctype="multipart/form-data">
Everything works as it should. Can anyone explain the potential risks involved with this?

Express CSRF token validation

I'm having issues with CSRF tokens. When I submit a form, a new XSRF-TOKEN is being generated but I think I'm generating two different tokens, I'm kinda confused. There's also a token called _csrf, so I see two different cookies in developer tools (XSRF-TOKEN and _csrf), _csrf doesn't change after a post.
What I want to do is to generate a new token for each post request and check whether it's valid or not. One thing I know that I should do it for security, but I stuck.
It has been a long day and I'm new into Express and NodeJS.
Here's my current setup.
var express = require('express')
, passport = require('passport')
, flash = require('connect-flash')
, utils = require('./utils')
, csrf = require('csurf')
// setup route middlewares
,csrfProtection = csrf({ cookie: true })
, methodOverride = require('method-override')
, bodyParser = require("body-parser")
, parseForm = bodyParser.urlencoded({ extended: false })
, cookieParser = require('cookie-parser')
, cookieSession = require('cookie-session')
, LocalStrategy = require('passport-local').Strategy
, RememberMeStrategy = require('../..').Strategy;
var app = express();
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.engine('ejs', require('ejs-locals'));
app.use(express.logger());
app.use(express.static(__dirname + '/../../public'));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(express.session({ secret: 'keyboard cat' }));
app.use(flash());
// Initialize Passport! Also use passport.session() middleware, to support
// persistent login sessions (recommended).
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.authenticate('remember-me'));
app.use(app.router);
app.use(csrf());
app.use(function (req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
res.locals.csrftoken = req.csrfToken();
next();
});
Routes
app.get('/form', csrfProtection, function(req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken()});
});
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
send.ejs (/form GET)
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>
Based on the amount of code you shared, I will mention a few things that don't look quite right to me:
1 . You may need to swap the lines below so that csrf runs before the routes.
app.use(csrf());
app.use(app.router);
2 . The csrftoken setup needs to also be placed before the routes.
app.use(csrf());
app.use(function (req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
res.locals.csrftoken = req.csrfToken();
next();
});
app.use(app.router);
3 . You'll need to use locals.csrftoken in your form:
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="<%= csrftoken %>">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>
the token in the cookie will be completely different than the one in the express session. you want to check for one or the other not both.
i would disable the cookies entirely! as it worked for me.
var csrfProtection = csurf({ cookie: false });
the author mentions it here
https://github.com/expressjs/csurf/issues/52
next you want to the "X-CSRF-Token" to the header on ajax post found here:
Express.js csrf token with jQuery Ajax
Below code is working for me. Let me know in case you still face issue.
As mentioned that you wish to use cookies, you have make csurf aware that you are using cookies for setting the CSRF token.
Step1: Configuration
var csrf = require('csurf');
var cookieparser= require('cookie-parser');
//cookieparser must be placed before csrf
app.use(bodyparser.urlencoded({extended:false}));
app.use(cookieParser('randomStringisHere222'));
app.use(csrf({cookie:{key:XSRF-TOKEN,path:'/'}}));
//add the your app routes here
app.use("/api", person);
app.use("/", home);
Step2: In the route,
res.render('myViewPage',{csrfTokenFromServer:req.csrfToken()});
Step3: Include a hidden field in the HTML for csrf token Example:
<form action="/api/person" method="POST">
<input type="hidden" name="_csrf" value=<%=csrfTokenFromServer %> />
First name:<br>
<input type="text" name="firstname" value="">
<br>
Last name:<br>
<input type="text" name="lastname" value="">
<br><br>
<input type="submit" value="Submit">
</form>
When we set csrf cookie it has default key _csrf. We can override it. So in my case I gave same name to cookie like this.
const csrf = csurf({cookie:{key:'XSRF-TOKEN'}});
app.get('/csrf-token', csrf, (req: Request, res: Response, next: NextFunction) => {
const newToken = req.csrfToken();
res.cookie('XSRF-TOKEN', newToken, {
httpOnly: true,
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production',
});
res.json({ csrfToken: newToken });
});
I dont know if you resolved the issue but its still will help if someone else looking for it.

Resources