I would like to test my node.js + express + passport.js test application (RESTful) with CURL. My code:
var express = require('express');
var routes = require('./routes');
var http = require('http');
var path = require('path');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
// Define the strategy to be used by PassportJS
passport.use(new LocalStrategy(
function(username, password, done) {
if (username === "admin" && password === "admin") // stupid example
return done(null, {name: "admin"});
return done(null, false, { message: 'Incorrect username.' });
}
));
// Serialized and deserialized methods when got from session
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
// Define a middleware function to be used for every secured routes
var auth = function(req, res, next){
if (!req.isAuthenticated())
res.send(401);
else
next();
};
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/test', auth, function(req, res){
res.send([{name: "user1"}, {name: "user2"}]);
});
app.post('/login', passport.authenticate('local'), function(req, res) {
res.send(req.user);
});
Calling /test with curl and
curl "http://localhost:3000/test"
returns "Unauthorized" (this is correct so far).
Calling /login (POST) with curl and
curl --data "username=admin&password=admin" http://localhost:3000/login
works. But at the next request my login was "forgotten".
Is this because curl can't handle sessions? Is there any workaround?
Is my procedure correct for a RESTful application?
First, make curl save cookies when you log in with
curl --cookie-jar jarfile --data "username=admin&password=admin" http://localhost:3000/login
Read the stored cookies when accessing /test:
curl --cookie jarfile "http://localhost:3000/test"
Some modifications to the app itself were needed before it worked on my machine (Ubuntu 12.04) with Node.js v0.10.26 and Express 3.5.0. I generated a new Express app with express --sessions nodetest and edited the code in app.js to be as you see below. Once I had the dependencies installed I ran the app and it worked with the curl commands.
app.js
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var app = express();
// Define the strategy to be used by PassportJS
passport.use(new LocalStrategy(
function(username, password, done) {
if (username === "admin" && password === "admin") // stupid example
return done(null, {name: "admin"});
return done(null, false, { message: 'Incorrect username.' });
}
));
// Serialized and deserialized methods when got from session
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
// Define a middleware function to be used for every secured routes
var auth = function(req, res, next){
if (!req.isAuthenticated())
res.send(401);
else
next();
};
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(express.cookieParser('your secret here'));
app.use(express.session());
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/test', auth, function(req, res){
res.send([{name: "user1"}, {name: "user2"}]);
});
app.post('/login', passport.authenticate('local'), function(req, res) {
res.send(req.user);
});
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
curl script
#!/bin/sh
# curl-login.sh
rm jarfile
echo --- login
curl --cookie-jar jarfile --data "username=admin&password=admin" http://localhost:3000/login
echo --- test
curl --cookie jarfile "http://localhost:3000/test"
Console log with curl output
$ node app &
$ sh curl-login.sh
--- login
POST /login 200 2ms - 21b
{
"name": "admin"
}--- test
GET /test 200 1ms - 60b
[
{
"name": "user1"
},
{
"name": "user2"
}
]
Note the use of
app.use(express.cookieParser('your secret here'));
app.use(express.session());
in app.js. Sessions did not work without the above two lines.
The code in your question also lacks the part where you create an HTTP server but I assume that's just a copy-paste issue; I'm referring to
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
Related
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 });
});
I am using PassportJS to authenticate users in my application. After a user logs in, the session is created, but soon after being redirected, the session appears to become undefined once again because it hasn't been saved. I found online that often times with redirects, the redirect completes before the session is saved, and so it's as if authentication never happened. The apparent solution is to use the req.session.save function so that redirects will only happen after the session is saved. However, I am getting an error log of "TypeError: req.session.save is not a function." Can somebody please help?
Here is my code for app.js.
var express = require('express'),
passport = require('passport'),
session = require('express-session'),
bodyParser = require('body-parser'),
RedisStore = require('connect-redis')(session),
redis = require('redis'),
logger = require('morgan'),
errorHandler = require('express-error-handler'),
site = require('./site'),
oauth2 = require('./oauth2'),
port = process.env.PORT || 8080;
var app = express();
var redisClient = redis.createClient(8080, 'localhost');
// use sessions for tracking logins
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: true,
store: new RedisStore({
client: redisClient,
host: "pub-redis-14280.us-central1-1-1.gce.garantiadata.com",
port: 12543,
ttl: 260
})
}));
app.use(logger('dev'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json({ type: 'application/json' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(errorHandler({ dumpExceptions: true, showStack: true }));
// use ejs as file extension for views
app.set('view engine', 'ejs');
app.use(express.static(__dirname + '/views'));
// use passport
require('./auth');
// Account linking
app.get('/', site.index);
app.get('/login', site.loginForm);
app.post('/login', site.login);
app.get('/logout', site.logout);
app.get('/authorize', oauth2.authorization);
app.post('/authorize/decision', oauth2.decision);
// set up local server
if (module === require.main) {
// [START server]
// Start the server
var server = app.listen(process.env.PORT || 8080, function () {
var port = server.address().port;
console.log('App listening on port %s', port);
});
// [END server]
}
module.exports = app;
site.js:
var passport = require('passport');
var login = require('connect-ensure-login');
// get layout
exports.index = function (req, res) {
console.log("layout loaded");
res.render('layout');
}
// get login form
exports.loginForm = function (req, res) {
console.log("login page loaded");
res.render('login');
}
// post login form
exports.login = [
passport.authenticate('local'),
function (req, res) {
req.session.save(function (err) {
res.redirect('/');
});
}
]
// logout
exports.logout = function (req, res) {
req.logout();
res.redirect('/');
}
Passport serialize/deserialize user:
passport.serializeUser(function(id, done) {
console.log("serializing user");
done(null, id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializing user");
done(null, id);
});
In my passport authentication, I return the user id for simplicity, since that's all I need to represent users in my system.
In case anybody else is still having this issue (like me), try following Nathan's comment above and debug your connection to your redis/mongo/etc store.
What worked for me was I had my redis host set to http://localhost so I swapped it to 127.0.0.1 (local development of course) and everything immediately worked.
Is it possible to do basic auth in Node.js just like in Apache?
http://doc.norang.ca/apache-basic-auth.html
I know that if using Express or Connect I can add middle-ware functionality and do user verification, but I'm trying to restrict the whole area (I don't need to authenticate users from a database just a couple of defined users) - I'm using Ubuntu.
https://github.com/kaero/node-http-digest
That's something I can do, but I'm not sure if "exposing" or directly writing the user and password in the code is secure enough.
Many thanks.
Passport provides a clean mechanism to implement basic auth. I use it in my Node.js Express app to protect both my Angularjs-based UI as well as my RESTful API. To get passport up and running in your app do the following:
npm install passport
npm install passport-http (contains "BasicStrategy" object for basic auth)
Open up your app.js and add the following:
var passport = require('passport')
var BasicStrategy = require('passport-http').BasicStrategy
passport.use(new BasicStrategy(
function(username, password, done) {
if (username.valueOf() === 'yourusername' &&
password.valueOf() === 'yourpassword')
return done(null, true);
else
return done(null, false);
}
));
// Express-specific configuration section
// *IMPORTANT*
// Note the order of WHERE passport is initialized
// in the configure section--it will throw an error
// if app.use(passport.initialize()) is called after
// app.use(app.router)
app.configure(function(){
app.use(express.cookieParser());
app.use(express.session({secret:'123abc',key:'express.sid'}));
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.set('view options', {
layout: false
});
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(__dirname + '/public'));
app.use(passport.initialize());
app.use(app.router);
app.use(logger);
});
// Routes
app.get('/', passport.authenticate('basic', { session: false }), routes.index);
app.get('/partials/:name', routes.partials);
// JSON API
app.get('/api/posts', passport.authenticate('basic', { session: false }), api.posts);
app.get('/api/post/:id', passport.authenticate('basic', { session: false }), api.post)
// --Repeat for every API call you want to protect with basic auth--
app.get('*', passport.authenticate('basic', { session: false }), routes.index);
Put this
app.use(express.basicAuth(function(user, pass) {
return user === 'test' && pass === 'test';
}));
before the line to
app.use(app.router);
to protect all routes with http basic auth
Have a look at:
user authentication libraries for node.js?
It does not answer your question 100% - but maybe it helps.
I think good choice could be http-auth module
// Authentication module.
var auth = require('http-auth');
var basic = auth.basic({
realm: "Simon Area.",
file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass ...
});
// Application setup.
var app = express();
app.use(auth.connect(basic));
// Setup route.
app.get('/', function(req, res){
res.send("Hello from express - " + req.user + "!");
});
Short summary of the problem: I'm using a new version of express (4.9.0) on a Node.js website which has a few public pages and a few private pages, which I'm putting behind a Google OAuth2 login, using PassportJS. When I request a page behind the sign-in, the user keeps being asked to sign-in; req.isAuthenticated() isn't "sticky". I've seen some comments online that there are can be problems with cookie parser versions being incompatible, so that could be the issue here.
Here is my (rather long) app.js:
var express = require('express');
var http = require('http');
var path = require('path');
var passport = require('passport');
var googleStrategy = require('passport-google-oauth').OAuth2Strategy;
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var app = express();
app.use(cookieParser());
app.use(bodyParser());
app.use(session({ secret: 'mysecret' }));
app.set('view engine', 'html');
app.set('views', path.join(__dirname, 'views'));
app.engine('html', require('ejs').renderFile);
app.set('port', process.env.PORT || 3000);
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
passport.use(new googleStrategy({
clientID: clientId,
clientSecret: secret,
callbackURL: "http://127.0.0.1:3000/auth/google/callback"
},
function (accessToken, refreshToken, profile, done) {
process.nextTick(function () {
return done(null, profile);
});
}
));
app.get('/auth/google',
passport.authenticate('google', { scope: ['https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'] }),
function(req, res){
});
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function(req, res) {
console.log("successfully authenticated with google");
res.redirect('/');
});
app.get('/login', function(req, res){
res.redirect('/auth/google');
});
app.get('/privatepage', ensureAuthenticated, function(req, res) {
var dostuff = module.listThings(function(rows) {
res.render('somepage.html', {
title : "some page",
data : rows
});
});
});
function ensureAuthenticated(req, res, next) {
console.log("in ensureAuth", req.isAuthenticated());
console.log("session data", req.session);
console.log("user data", req.user);
if (req.isAuthenticated()) { return next(); }
res.redirect('/login');
}
var server = app.listen(3000, function() {
var host = server.address().address
var port = server.address().port
console.log('App listening at http://%s:%s', host, port)
})
After the first login, if I visit the /privatepage URL, I get re-directed through Google and land on the homepage again. This is what's in the log from those console.log lines:
in ensureAuth false
session data { cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {} }
user data undefined
I assume this is some session issue where it doesn't store the user's logged in state, but I'm a node newbie & am a little stuck on how to fix this. I'm running on a single computer, so it's not a multiple machines issue. Any debugging suggestions, or ideas on the conflict causing the logged-in state not to be stored?
So I'm stuck as to why this isn't working. Whenever I use the cookie for maxAge, it just doesn't allow me to login. It redirects me to /, so the callback is working but the session data is lost for some reason. Can anybody assist me?
Thanks in advance.
/**
* Module dependencies.
*/
var express = require('express');
var routes = require('./routes');
var login = require('./routes/login');
var register = require('./routes/register');
var http = require('http');
var path = require('path');
var MongoClient = require('mongodb');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var app = express();
// all environments
app.set('port', process.env.PORT || 3001);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.cookieParser());
app.use(express.session({
secret: '1234567890QWERT',
cookie: {maxAge: 30}
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
var dbc;
var User;
MongoClient.connect('mongodb://127.0.0.1:27017/redacted', function(err, db) {
dbc = db;
User = dbc.collection('users');
});
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ email: username }, function(err, user) {
if (password == user.password) {
console.log("Login success!");
// Allows us to keep a log of when the user logs in in:
// user['loggedin'][]
// db.users.update({email: ""}, { $push : {loggedin: new Date()} } )
if(!err) done(null, user );
}
else done(err, null)
});
}
));
passport.serializeUser(function(user, done) {
done(null, {
id: user["_id"],
name: user["name"],
email: user["email"],
registered: user["registered"],
password: user["password"]
});
});
passport.deserializeUser(function(id, done) {
console.log(id);
User.find({_id: id._id}, function(err, user) {
done(err, user);
});
});
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
// User stuff
app.get('/login', login.get);
app.post('/login',
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login?m=fail'
})
);
app.get('/register', register.get);
app.post('/register', register.post);
app.get('/logout', function(req, res){
req.logout();
res.redirect('/');
});
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
Your session cookie is set to last 30ms.
Per Express documentation, maxAge sets the expiration date of the cookie in ms.
Change cookie: {maxAge: 30} to something like cookie: {maxAge: 24*60*60*1000} for a longer-lasting session cookie.