NodeJs + Passport. Cookie persistence in Windows vs MacOS - node.js

I'm using PassportJS and NodeJS as backend for a website server. It uses the Passport-Local strategy. I develop the website in a MacOSX environment.
The server serves pages for information about our company and our commercial product.
It also serves help files (HTML web help) generated using Help and Manual.
The software that we sell runs on Windows and is programmed in Delphi VCL. We set it up so that it will redirect to our web help when pressing F1 using contextual ids like such :
procedure MyForm.OnHelp(blabla)
begin
ShellExecute( blabla, www.mywebsite.com/secure/help?contextid=202383)
end
All paths in www.mywebsite.com/secure are secure, and will require authentication with the passport.js middleware.
This is the relevant routing / passport configuration.
// CHECK AUTH METHOD
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
req.session.target = req.originalUrl;
req.session.save(function (err) {
res.redirect('/login')
});
}
// PASSPORT LOGIN STRATEGY
passport.use(
new passportLocal.Strategy({
usernameField: "username",
passwordField: "password"
},
function (email, password, done) {
if (!email) {
return done(null, false);
}
if (!password) {
return done(null, false);
}
if (password.toLowerCase() === 'ASTUPIDPASSWORD') {
return done(null, {email: email});
} else {
return done(null, false);
}
}
));
// PASSPORT SERIALIZE
passport.serializeUser(function (user, done) {
done(null, user.email);
});
// PASSPORT DESERIALIZE
passport.deserializeUser(function (email, done) {
done(null, {email: email});
});
// OTHER AMAZING ROUTES
router.use('/secure', ensureAuthenticated);
router.get('/secure/help', function (req, res) {
res.redirect('/secure/help/index.html');
});
This is the express configuration
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var compression = require('compression');
var fs = require('fs');
var passport = require('passport');
var session = require('express-session');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'client', 'views'));
app.set('view engine', 'jade');
app.use(compression());
app.use(favicon(__dirname + '/client/images/favicon.png'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true})); //
app.use(cookieParser());
app.use(require('less-middleware')(path.join(__dirname, 'client')));
app.use(express.static(path.join(__dirname, 'client')));
app.use(session({
saveUninitialized : true,
resave : false,///
secret: 'adamngoodsecret
}));
app.use(passport.initialize());
app.use(passport.session());
var routes = require('./routes/index');
app.use('/', routes);
When I develop in the MacOSX environment, I will login once. Then, I will be able to return to the secure zone because I guess I will have a cookie set up, regardless if I'm using safari or chrome.
Though, when I test it in a Windows environment, the problem occurs.
I open the broswer, type in www.mywebsite.com/secure/help?blabla
I am asked for login. OK
I enter login. OK
I am redirected to the initial requested url. OK
I close the tab WITHOUT CLOSING THE BROWSER. OK
I request the www.mywebsite.com/secure/help?blabla URL. OK
I can access the resource. OK
I close the browser. OK.
I open a new broser window. OK.
I request the www.mywebsite.com/secure/help?blabla. OK
I get asked for credentials. FAIL
So, whenever I close the browser window (Safari, Chrome) in Windows 10 environment, I get asked for the credentials again.
In MacOSX, even if I close the browser, I do not get asked the credentials again.
It is very unconvenient for my users to have to enter their credentials each time on the Windows environment.
Can anyone point me in the right direction in order to solve this? It seems the cookies in the Windows 10 environment are reset whenever the browser window is closed, though I'm not sure.
Thanks.

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 });
});

CORS error when sending a request from my reactjs app to my express server, which redirects to Spotify's API [duplicate]

This question already has answers here:
Access-Control-Allow-Origin denied spotify api [duplicate]
(1 answer)
Allowing frontend JavaScript POST requests to https://accounts.spotify.com/api/token endpoint
(1 answer)
Spotify Auth + frontend JavaScript + Client Credential Flow
(1 answer)
Getting Spotify API access token from frontend JavaScript code
(1 answer)
Authentication Request to Spotify Web API Rejected
(1 answer)
Closed 1 year ago.
Any other requests through my react app seems to be working fine, until I try to res.redirect to somewhere else.
I somewhat understand why it does not work, but I cant figure out how to properly implement something similar to that.
Code for references:
React app.js:
function loginToSpotify() {
axios.get('http://localhost:4000/login')
.then(res => {
console.log(res)
})
.catch(err => {
console.log("error!")
})
}
express server.js:
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var cors = require('cors');
var app = express();
app.use(cors())
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
module.exports = app;
express index.js login call:
const { default: axios } = require('axios');
var express = require('express');
var router = express.Router();
var querystring = require('querystring')
var SpotifyWebApi = require('spotify-web-api-node');
/* GET home page. */
let spotifyApi = new SpotifyWebApi({
clientId:'c2b60e83cbdb4b5ba923140f0c32ac8f',
clientSecret:'d45b554801ab4333b89a36bdbf04fad7'
})
let clientId = 'c2b60e83cbdb4b5ba923140f0c32ac8f'
let clientSecret = 'd45b554801ab4333b89a36bdbf04fad7'
let scopes = 'user-read-playback-state user-read-currently-playing'
let redirectUri = 'http://localhost:4000/token'
router.get('/login', (req, res) => {
res.redirect('https://accounts.spotify.com/authorize' + '?response_type=code&client_id=' + clientId + '&scope=' + encodeURIComponent(scopes) + '&redirect_uri=' + encodeURIComponent(redirectUri))
})
It never even gets redirected back, It just throws this error every time:
Access to XMLHttpRequest at 'https://accounts.spotify.com/authorize?response_type=code&client_id=<removed by me just in case>&scope=user-read-playback-state%20user-read-currently-playing&redirect_uri=http%3A%2F%2Flocalhost%3A3000' (redirected from 'http://localhost:4000/login') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I thought that if I make a call from my react app to my server, and then my server redirects to spotify's api it would be fine, but it still gives me this error.
also note that if I access the login page of my server from the browser, it will work but it does not like the "two jumps" I guess
I have seen this thread and am still a bit confused:
Access-Control-Allow-Origin denied spotify api
If i make the call from my client to my server, then the server makes the call to the token, shouldn't it work?
How can I implement the login properly?
Step one to avoid CORS issues is to make sure your website on Spotify is 'localhost:4000'.
The redirect is most likely causing the concern. You don't want to use a redirect. You want Spotify to authenticate the user and then redirect them back to your website with a token.
I would suggest using passportJS, this is a much more secure way if you are new to handling tokens and such.
Create a strategy as such
const passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use(new SpotifyStrategy({
clientID: "c2b60e83cbdb4b5ba923140f0c32ac8f",
clientSecret: "d45b554801ab4333b89a36bdbf04fad7",
callbackURL: "http://localhost:4000/auth/spotify/callback"
},
function(accessToken, refreshToken, profile, done) {
return done(null, profile);
}
));
PassportJS Spotify Docs
const passport = require('passport');
require('./passport')
app.use(passport.initialize());
app.use(passport.session());
app.get('/auth/spotify',passport.authenticate('spotify'));
app.get('/auth/spotify/callback',passport.authenticate('spotify', { failureRedirect: '/auth/error' }),
function(req, res) {
res.redirect('/');
});
app.get('/logout', (req, res) => {
req.session = null;
req.logout();
res.redirect('/');
})

Access to azure web-app using passport-azure-ad-oauth2

I am trying to authenticate my Azure Web App with passport-azure-ad-oauth2 in Node JS using express
I have tried to follow along with the documentation found here: https://github.com/auth0/passport-azure-ad-oauth2. I believe I have gotten the client ID, secret and callback URI correct...
When I go to localhost:3000, it redirects successfully to Office365 sign in. When I choose the pre-selected account, it just keeps looping back to the "select account"
When trying to sign in using an incognito window in Chrome it gives me the error: The reply URL specified in the request does not match the reply URLs configured for the application: '***appID'.
My code is obviously wrong and am hoping someone is able to possibly help me get it set up correctly.
Thanks in advance!!
My code is here
const express = require("express");
const bodyParser = require("body-parser")
const session = require('express-session');
const passport = require("passport");
const ejs = require("ejs");
const jwt = require("jwt-simple")
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy;
const app = express();
app.use(express.static("public"));
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(passport.initialize());
app.use(bodyParser.urlencoded({ extended: false }));
passport.use(new AzureAdOAuth2Strategy({
clientID: 'azure client ID',
clientSecret: 'secret',
callbackURL: 'http://localhost:3000/auth/aad/callback',
// resource: '00000002-0000-0000-c000-000000000000',
// tenant: 'contoso.onmicrosoft.com'
},
function (accessToken, refresh_token, params, profile, done) {
var waadProfile = profile || jwt.decode(params.id_token, '', true);
console.log(waadProfile);
User.findOrCreate({ id: waadProfile.upn }, function (err, user) {
done(err, user);
});
}));
app.get("/",passport.authenticate('azure_ad_oauth2'));
app.get('/auth/aad/callback',
passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login' }),
function (req, res) {
console.log(req);
console.log(res);
res.render('index');
});
app.listen(process.env.PORT || 3000, function() {
console.log("Server started on Port 3000");
});
The callback URL in your code is not the same as the one set on Azure.
That' why it says:
The reply URL specified in the request does not match the reply URLs configured for the application: '***appID'.
Set the correct URL on Azure to fix this.
For the infinite redirection issue, clear the cache and cookie in your browser and it should work.
However, if you don't correct the callback URL, it's going to happen again.

How can I create a basic login similar to Apache's htpasswd using Express and Node? [duplicate]

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 + "!");
});

Express + Passport + Session. Executes a query for every page load

I'm using Express 4.2.0 with passport 0.2.0.
The express-session middleware that I am using is 1.2.1
I'm relatively new to node authentication so please bear with me.
I noticed that for everyone page load, passport is executing a db request:
Executing (default): SELECT * FROM "users" WHERE "users"."user_id"=7 LIMIT 1;
This doesn't make sense to me as I would think that the user has already been authenticated and serialized. And the session is now stored in the browser cookie. I also checked that I do have a cookie session stored in my browser.
Here are my app.js configuration:
var express = require('express');
var path = require('path');
var favicon = require('static-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
//var sys = require('sys');
var cons = require('consolidate');
/* sequelize */
var db = require('./models');
/* passport and its friends */
var passport = require('passport');
var flash = require('connect-flash');
var session = require('express-session');
/* routes */
var routes = require('./routes/index');
var users = require('./routes/users');
//filesystem middleware
//var fs = require('fs');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.engine('dust', cons.dust);
app.set('view engine', 'dust');
app.use(favicon());
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(cookieParser('hashionhashion'));
app.use(require('less-middleware')(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({ secret: 'hashionhashion' })); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
//routing
app.use('/', routes);
app.use('/users', users);
and here is the passport serialize/deserialize function:
passport.serializeUser(function(user, done) {
done(null, user.user_id);
});
// used to deserialize the user
passport.deserializeUser(function(id, done) {
db.User.find({ where: {user_id: id} }).success(function(user){
done(null, user);
}).error(function(err){
done(err, null);
});
});
Executing a query on each request to fetch the authenticated user data sometimes is not a good practice(but it is commonly used in apps).
In one of my apps, i only needed id of the authenticated user rather than all data (like password,last_login_date,register_date, ...) so i changed passport configs like below:
passport.serializeUser(function(user, done) {
done(null, {id: user.id});
});
// used to deserialize the user
passport.deserializeUser(function(object, done) {
done(null, object); // object is: {id: user_id}
});
and then in your controllers you can get user id from session:
router.get('/', function(req, res, next)=> {
const userId = req.user.id;
// now do a database query only if needed :)
});
you can also define a custom middleware to fetch user from db and set to
req.user to use in next middleware:
function fetchUser (req, res, next) {
db.User.find({ where: {id:req.user.id} }).success(function(user){
req.user = user;
next(); // will trigger next middleware
}).error(function(err){
next(err); // will trigger error handler
});
}
router.get('/', fetchUser, function(req, res, next)=> {
const user = req.user;
// now user is a object that is fetched from db and has all properties
});
In this approach you'll execute query for retrieving user from db only if it's needed and not for all routes.

Resources