I am very new to both JS and NodeJs/Express. I am writing a proof of concept MVP node/express app that uses passport for authentication (via social logins).
I have written the server and installed all required packages and so far everything (apart from the authentication bit works).
Note: I have already set up my credentials at the various social media company's ends, so this is not the problem.
Here is a snippet of my code:
app.js
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
compression = require('compression'),
shouldCompress = (req, res) => {
if (req.headers['x-no-compression']) {
// don't compress responses if this request header is present
return false;
}
// fallback to standard compression
return compression.filter(req, res);
};
const app = express();
// EJS
app.use(expressLayouts);
app.set('view engine', 'ejs');
// Parsing related
app.use(express.urlencoded( { extended: false })); //Parse URL-encoded bodies
app.use(express.json()); //Used to parse JSON bodies
app.use(compression({
filter:shouldCompress,
threshold: 3
}));
app.use(express.static('public'));
app.disable('x-powered-by');
// Initialize Passport and restore authentication state, if any, from the session.
var passport = require('passport');
app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session())
// Routes
app.use('/', require('./routes/index'));
app.use('/member', require('./routes/users'));
const PORT = process.env.PORT || 5000;
app.listen(PORT, console.log(`Server started on port: ${PORT}`));
routes/users.js
const express = require('express');
const router = express.Router();
/* GET authentication funcs */
let authentication = require('../controllers/auth.js');
router.get('/subscribe', (req, res) => { res.render('subscribe'); });
router.post('/subscribe', authentication.subscribe);
module.exports = router;
controllers/auth.js
require('dotenv').config();
const model = require("../models");
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const FacebookStrategy = require('passport-facebook').Strategy;
passport.serializeUser(function(user, done) {
done(null, user)
});
passport.deserializeUser(function(email, done) {
done(null, email)
})
function validateLead(req, done) {
mail = req.body.lead_email;
console.log('validateLead() called!');
models.Lead.findOne({
where: {
email: email
}
}).then(/* some logic */).catch();
} else {
// email already taken ..
return done(null, false, {
message: 'This email address is already subscribed'
});
}
}).catch((err) => {
console.log('An error occurred!', err);
});
}
exports.subscribe = function(req, res, next) {
switch (req.body.source) {
case 'facebook':
passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
callbackURL: process.env.FACEBOOK_CALLBACK_URL
},
function(accessToken, refreshToken, fbProfile, done) {
console.log('FB callback!');
profile = {
'email': fbProfile.email,
'firstName': '',
'LastName': '',
'leadSource': '',
'tags': [],
};
return validateLead(req, done, profile);
}
));
break;
default:
console.log('Unknown');
}
}
views/test.eps
<a href='#'><i id='facebook' class='foobar'></i></a>
(simplified) views/layout.eps
$(document).ready(function(){
$('i.foobar').click(function(e){
$.ajax({
method: "POST",
url: "/member/subscribe",
data: {
"source": $(this).attr('id')
},
dataType: "json",
timeout: 5000 // 5000ms
}).done(function(data) {
// is called if request is successful
console.log(data.fridge);
}).fail(function(jqXHR, status) {
// is called if request fails or timeout is reached
alert('Request could not complete: ' + status);
});
});
});
I have put console.log() messages in controllers/auth.js and I can see that the FB code branch is being reached. However, when log messages in validateLead() and the FB callback function are not being reached.
What is the cause of this, and how do I fix it?
A couple things I can see :
As far as I could see you didn't configure passportjs. You need to have a configuration file, which for you would be the controllers/auth.js. To configure passport you need to run require('./controllers/auth')(passport); in app.js.
For passport to be able to ingest that config you need to export them as a function that takes passport e.g. module.exports = passport => {passport.use('facebook')}
Your config file (in exports.subscribe) is not a format that passport will understand. Follow the Documentation on how to create that config file.
Passport provides you with authentication middleware, I am pretty sure that you cannot create "wrappers" for them like in controllers/auth.js. To access passport's auth functions you use passport.authenticate('facebook', callback())(req, res, next) in routes/users.js.
Passport only provides middleware to serialize and deserialize users.
Your deserialization is not yet set up. You need a call to the db to fetch the user from session store.
Related
in the last few hours I setup a backend express server. It works just fine and now I tryed to implement an authorization with help of a tutorial.
The login works, but when I try to open /authrequired (so basically a future page which needs a logged in user to work) I get the error message: "TypeError: req.isAuthenticated is not a function"
Here is my index.js file:
const express = require('express');
const fs = require('fs');
const http = require('http');
const https = require('https');
const path = require('path');
const uuid = require('uuid').v4;
const session = require('express-session');
const FileStore = require('session-file-store')(session);
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const users = [
{id: '2f24vvg', email: 'test#test.com', password: 'password'}
]
// configure passport.js to use the local strategy
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(email, password, done) => {
console.log('Inside local strategy callback')
// here is where you make a call to the database
// to find the user based on their username or email address
// for now, we'll just pretend we found that it was users[0]
const user = users[0]
if(email === user.email && password === user.password) {
console.log('Local strategy returned true')
return done(null, user)
}
}
));
// tell passport how to serialize the user
passport.serializeUser((user, done) => {
console.log('Inside serializeUser callback. User id is save to the session file store here')
done(null, user.id);
});
passport.deserializeUser((id, done) => {
console.log('Inside deserializeUser callback')
console.log(`The user id passport saved in the session file store is: ${id}`)
const user = users[0].id === id ? users[0] : false;
done(null, user);
});
const app = express();
app.enable('trust proxy')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(session({
genid: (req) => {
console.log('Inside the session middleware')
console.log(req.sessionID)
return uuid() // use UUIDs for session IDs
},
store: new FileStore(),
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
app.use(passport.session());
app.post('/login', (req, res, next) => {
console.log('Inside POST /login callback')
passport.authenticate('local', (err, user, info) => {
console.log('Inside passport.authenticate() callback');
console.log(`req.session.passport: ${JSON.stringify(req.session.passport)}`)
console.log(`req.user: ${JSON.stringify(req.user)}`)
req.login(user, (err) => {
console.log('Inside req.login() callback')
console.log(`req.session.passport: ${JSON.stringify(req.session.passport)}`)
console.log(`req.user: ${JSON.stringify(req.user)}`)
return res.send('You were authenticated & logged in!\n');
})
})(req, res, next);
})
function isAuthenticated (req, res, next) {
if (req.session.user) next()
else next('route')
}
app.get('/authrequired', isAuthenticated, function (req, res) {
res.send('you hit the authentication endpoint\n')
})
app.use(express.static(path.resolve(__dirname, 'build')));
app.use(express.json());
// Redirect from http port to https
http.createServer(function (req, res) {
res.writeHead(301, { "Location": "https://" + req.headers['host'].replace(80,433) + req.url });
console.log("http request, will go to >> ");
console.log("https://" + req.headers['host'].replace(80,433) + req.url );
res.end();
}).listen(80, () => console.info('Listening on port', 80))
//Start https server
https.createServer({
key: fs.readFileSync('./ssl/privkey.key'),
cert: fs.readFileSync('./ssl/cert.cer'),
keepAlive: true
}, app).listen(443, () => console.info('Listening on port', 443));
Anyone got a clue? I saw similar questions on stackoverflow, but nothing worked for me.
There is no such function isAuthenticated. That's why you get the error.
Try replace it with if(req.session.user) {
Or by the example in express-session
// middleware to test if authenticated
function isAuthenticated (req, res, next) {
if (req.session.user) next()
else next('route')
}
app.get('/', isAuthenticated, function (req, res) {
// this is only called when there is an authentication user due to isAuthenticated
res.send('hello, ' + escapeHtml(req.session.user) + '!' +
' Logout')
})
EDIT: You also need to use passport session middleware, by adding
app.use(passport.session());
Make sure it is coming after the session init.
So I'm using this post as an example to test POST/GET. I've tested actions that I wanted to do which are Follow and Retweet. But now the problem is this code works on Express and my project is in ReactJS so how do I convert these code to let this work in React?
Here's my code:
var Twit = require('twit');
var express = require('express');
var path = require('path');
var passport = require('passport');
var cookieParser = require('cookie-parser');
var Strategy = require('passport-twitter').Strategy;
var session = require('express-session');
passport.use(new Strategy({
consumerKey: '...',
consumerSecret: '...',
callbackURL: 'http://localhost:3000/twitter/return'
}, function(token, tokenSecret, profile, callback) {
const configs = createConfigs(token, tokenSecret);
// Post to twitter
var Twit = require('twit')
var T = new Twit({
consumer_key: '...', //get this from developer.twitter.com where your app info is
consumer_secret: '...', //get this from developer.twitter.com where your app info is
access_token: token,
access_token_secret: tokenSecret,
timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests.
strictSSL: true, // optional - requires SSL certificates to be valid.
})
// tweet hello world
T.post('statuses/update', { status: 'hello world!' }, function(err,
data, response) {
console.log(data)
})
return callback(null, profile);
}));
passport.serializeUser(function(user, callback) {
callback(null, user);
})
passport.deserializeUser(function(obj, callback) {
callback(null, obj);
})
const app = express();
const port_no = 5555
//view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// Server Setup
app.listen(port_no, () => {
console.log('port running atport number : 5555')
})
app.use(cookieParser());
app.use(session({secret: 'whatever', resave: true, saveUninitialized: true}))
app.use(passport.initialize())
app.use(passport.session())
app.get('/' , (req,res) => {
res.render('testpage')
})
app.get('/twitter/login', passport.authenticate('twitter'))
app.get('/twitter/return', passport.authenticate('twitter', {
failureRedirect: '/'
}), function(req, res) {
res.redirect('/')
})
module.exports = app;
So the code above is mainly to login into user's twitter then make POST action according to the API. So I would need some help so that I can make this thing works in my React project.
I am new here.
I am trying to develop a web application and I have hit a brick wall which I've been stuck on for a while now.
When running my app.js file all pages show up except for the chatroom. Could someone please take a look at the code and help me out?
I haven't included my code for register and login etc. as they are working fine. When using their router.get and .post concepts for the chatroom I just receive a "Cannot GET /chatroom" message.
Chatroom.js:
const express = require('express');
const router = express.Router();
const socketClient = require('socket.io').listen(4000).sockets;
//Chatroom page: Credit - Traversy Media - https://www.youtube.com/watch?v=hrRue5Rt6Is&t=1542s
router.get('/chatroom', (req, res) => res.render('chatroom.ejs'));
router.post('/chatroom', (req, res) => {
// Connect to Socket.io
socketClient.on('connection', function (socket) {
let chat = db.collection('UsersDB/chats');
// Create function to send status
sendStatus = function (s) {
useUnifiedTopology: true,
socket.emit('status', s);
}
// Get chats from mongo collection
chat.find().limit(100).sort({ _id: 1 }).toArray(function (err, res) {
if (err) {
throw err;
}
// Emit the messages
socket.emit('output', res);
});
// Handle input events
socket.on('input', function (data) {
let name = data.name;
let message = data.message;
// Check for name and message
if (name == '' || message == '') {
// Send error status
sendStatus('Please enter a name and message');
} else {
// Insert message
chat.insert({ name: name, message: message }, function () {
socketClient.emit('output', [data]);
// Send status object
sendStatus({
message: 'Message sent',
clear: true
});
});
}
});
// Handle clear
socket.on('clear', function (data) {
// Remove all chats from collection
chat.remove({}, function () {
// Emit cleared
socket.emit('cleared');
});
});
});
});
app.js:
const express = require('express');
const expressLayouts = require('express-ejs-layouts');
const mongoose = require('mongoose');
const passport = require('passport');
const flash = require('connect-flash');
const session = require('express-session');
const app = express();
// Passport Config
require('./config/passport')(passport);
// DB Config
const db = require('./config/keys').mongoURI;
// Connect to MongoDB
mongoose
.connect(
db,
{ useNewUrlParser: true }
)
.then(() => console.log('MongoDB Connected'))
.catch(err => console.log(err));
// EJS
app.use(expressLayouts);
app.set('view engine', 'ejs');
// Express body parser
app.use(express.urlencoded({ extended: true }));
// Express session
app.use(
session({
secret: 'secret',
resave: true,
saveUninitialized: true
})
);
// Passport middleware
app.use(passport.initialize());
app.use(passport.session());
// Connect flash
app.use(flash());
// Global variables
app.use(function(req, res, next) {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
// Routes
app.use('/', require('./routes/index.js'));
app.use('/users', require('./routes/users.js'));
app.use('/chatroom', require('./routes/chatroom.js'));
const PORT = process.env.PORT || 5000;
app.listen(PORT, console.log(`Server started on port ${PORT}`));
First and foremost I'd like to thank Cranky Coder; Prakher Londe; and Sunil Lulla for your replies.
I ended up figuring out how to make the chatroom show up. As mentioned before my other pages(login and register etc.) were showing up fine. Unlike the chatroom I had the backend for these in a users.js file. So what I decided to do was move my chatroom.js code into my users.js which was then called in the app.js(the file I run) with app.use('/users', require('.routes/users.js')).
I then changed my router.get code to:
router.get('/chatroom', (req, res) => res.render('chatroom', {
user: req.user
}));
Then in my router.post (see OP for context) I added a res.redirect('/users/chatroom'); before closing with the final bracket.
Lastly I of course edited my .ejs file to have
I found that doing it this way is even better because the chatroom cannot be accessed unless one is logged in.
Thank you again for all your replies and although this is a bit of a specific problem I do hope this helps someone in the future.
I'm trying to configure the Express server I have created, to pass the SSL certificate and go from http to https.
I read the Express documentation but I can not find the solution. They proposed things to me like Lets Encrypt but it does not support Node.js I do not know if I should modify the hosts file, which I already modified to run the application, or what I have to do. I saw a form, but it only works on Unix system. I show the way in which I have configured the file of the server in case they can help me, it took three days looking for ways to do it without success. The ones I saw do not support Node.js. Thank you
I EDIT THE QUESTION:
Sorry for not including more details, the question is that my application is not in production and my domain is provisional: michaelgram.test. I think that with that Lets Encrypt does not grant me the certificates. I do not know what else to do.The issue is that the application is hosted locally, on my computer
I edit again:
Forgive, forget to say that my purpose is to create the certificate for an application in which you can make the registration to Facebook and tried the methods that my colleagues kindly offered, but it did not work, thanks to the new facebook policy.
If you have another idea, then my domain would be michaelgram.test
thank you and forgive the inconvenience, for not doing well the question.
let express = require('express');
let aws = require('aws-sdk');
let multer = require('multer');
let multerS3 = require('multer-s3');
let ext = require('file-extension');
let cookieParser = require('cookie-parser');
let bodyParser = require('body-parser');
let expressSession = require('express-session');
let passport = require('passport');
let michaelgram = require('michaelgram-client');
let auth = require('./auth')
let config = require('./config');
let port = process.env.PORT || 5050;
let client = michaelgram.createClient(config.client);
let s3 = new aws.S3({
accessKeyId: config.aws.accessKey,
secretAccessKey: config.aws.secretKey
});
let storage = multerS3({
s3: s3,
bucket: 'michaelgram',
acl: 'public-read',
metadata: function (req, file, cb) {
cb(null, { fieldName: file.fieldname })
},
key: function (req, file, cb) {
cb(null, +Date.now() + '.' + ext(file.originalname))
}
});
let upload = multer({ storage: storage }).single('picture');
let app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(expressSession({
secret: config.secret,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
app.set('view engine', 'pug');
app.use(express.static('public'));
passport.use(auth.localStrategy);
passport.use(auth.facebookStrategy);
passport.deserializeUser(auth.deserializeUser);
passport.serializeUser(auth.serializeUser);
app.get('/', function (req, res) {
res.render('index', { title: 'Michaelgram' });
})
app.get('/signup', function (req, res) {
res.render('index', { title: 'Michaelgram - Signup' });
})
app.post('/signup', function (req, res) {
let user = req.body;
client.saveUser(user, function (err, usr) {
if (err) return res.status(500).send(err.message)
debugger
res.redirect('/signin');
});
});
app.get('/signin', function (req, res) {
res.render('index', { title: 'Michaelgram - Signin' });
})
app.post('/login', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/signin'
}));
app.get('/auth/facebook', passport.authenticate('facebook', { scope: 'email' }));
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
successRedirect: '/',
failureRedirect: '/signin'
}));
function ensureAuth (req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.status(401).send({ error: 'not authenticated' })
}
app.get('/api/pictures', function (req, res, next) {
let pictures = [ ];
setTimeout(function () {
res.send(pictures);
}, 2000)
});
app.post('/api/pictures', ensureAuth,function (req, res) {
upload(req, res, function (err) {
if (err) {
return res.send(500, "Error uploading file");
}
res.send('File uploaded');
})
})
app.get('/api/user/:username', (req, res) => {
const user = {
username: 'miguelito',
avatar: '',
pictures: [ ]
}
res.send(user);
})
app.get('/:username', function (req, res) {
res.render('index', { title: `Michaelgram - ${req.params.username}` });
})
app.get('/:username/:id', function (req, res) {
res.render('index', { title: `Michaelgram - ${req.params.username}` });
})
app.listen(port, function (err) {
if (err) return console.log('Hubo un error'), process.exit(1);
console.log('Michaelgram escuchando en el puerto 5050');
})
When you're securing a web server with TLS you need two things:
private_key
server_certificate
To your first point, Lets Encrypt is a service that will support exactly what you're trying to do. The service they provide allows you to generate a trusted key and certificate which secures traffic on the server AS WELL AS let's others know that it was signed by a trusted cert authority. See https://letsencrypt.org/how-it-works/
If you JUST want tls you can generate a self signed certificate like so:
https://www.akadia.com/services/ssh_test_certificate.html
After you have your certificate and your key here is the https configuration for the server:
var https = require('https');
var fs = require('fs');
var express = require('express');
var options = {
key: fs.readFileSync('/etc/apache2/ssl/server.key'),
cert: fs.readFileSync('/etc/apache2/ssl/server.crt'),
requestCert: false,
rejectUnauthorized: false
};
var app = express();
var server = https.createServer(options, app).listen(3000, function(){
console.log("server started at port 3000");
});
See: create a trusted self-signed SSL cert for localhost (for use with Express/Node)
Once you have your key and crt ready you just launch the app with reference to them. These names just came using letsencrypt's default naming.
var options = {
key: fs.readFileSync(__dirname + '/components/ssl/privkey.pem'),
cert: fs.readFileSync(__dirname + '/components/ssl/fullchain.pem')
};
server = require('https').createServer(options, app);
After setting up the Drupal as this guide says: Drupal-passport I created a simple simple node app to test how it works.
It doesn't, I get the InternalOAuthError: Failed to obtain request token error.
Going through the strategy.js, I saw that my callbackURL is logging out undefined not exactly sure why. The callbackURL is set in my Drupal app
Also preforming a curl -i -XPOST http://extranet.local/rest/system/connect/ gives me exactly what I need
Here is my node.js code (keep in mind this is just supposed to test the drupal set up).
var express = require('express');
var passport = require('passport');
var dStrategy = require('passport-drupal').DrupalStrategy;
var passportDrupal = require('passport-drupal');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser')
var session = require('express-session');
var http = require('http');
var app = express();
var server = http.createServer(app);
app.use(cookieParser());
app.use(bodyParser());
app.use(session({ secret: 'SECRET' }));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new dStrategy({
consumerKey: "emDVp7P2LZFLPcN3cNCjLmrjrhQLnNv6",
consumerSecret: "mkbc3UYEuUQLNQRwLWo3B8zEk4ZrErKa",
providerURL: "http://extranet.local",
resourceEndpoint: "rest/system/connect", // <---- optional. Defaults to `rest/system/connect`
callbackURL: 'http://33.33.33.40:8888/auth/drupal/callback'
},
function(token, tokenSecret, profile, done) {
profile.oauth = { token: token, token_secret: tokenSecret };
done(null, profile);
}
));
app.get('/', function(req, res) {
res.writeHead(200);
res.end("This is root");
});
app.get('/auth/drupal',
passport.authenticate('drupal'),
function(req, res) {
// The request will be redirected to the Drupal website for
// authentication, so this function will not be called.
});
app.get('/auth/drupal/callback',
passport.authenticate('drupal', { failureRedirect: '/error' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/signedin');
});
app.get('/error', function(req, res) {
res.writeHead(200);
res.end("Could not sign in");
});
app.get('/signedin', function(req, res) {
res.writeHead(200);
res.end("signed in");
});
server.listen(8888, '33.33.33.40');
Any clues as to why or ideas are greatly appreciated
If you look into the strategy.js code of the library passport-drupal, you will see that the DrupalStrategy constructor does not expect a callbackURL property in the options parameter object and it also does not pass it further into the OAuthStrategy.
This is the code snippet that creates the parameter for the oauth strategy:
// Determine all necessary OAuth options
var oauthOptions = {
requestTokenURL: this._providerURL + '/oauth/request_token',
accessTokenURL: this._providerURL + '/oauth/access_token',
userAuthorizationURL: this._providerURL + '/oauth/authorize',
consumerKey: options.consumerKey,
consumerSecret: options.consumerSecret
};
OAuthStrategy.call(this, oauthOptions, verify);
It should be modified to pass the callbackURL, for example like this:
// Determine all necessary OAuth options
var oauthOptions = {
requestTokenURL: this._providerURL + '/oauth/request_token',
accessTokenURL: this._providerURL + '/oauth/access_token',
userAuthorizationURL: this._providerURL + '/oauth/authorize',
consumerKey: options.consumerKey,
consumerSecret: options.consumerSecret,
callbackURL: options.callbackURL// <==== THIS LINE WAS ADDED
};
OAuthStrategy.call(this, oauthOptions, verify);
I'm not sure that will solve your issue though. But I made a pull request