I'm trying to set up a web server using express. To access this server, users have to authenticate and for that, I use the basicAuth() middleware provided by Express. It works perfectly, except that I do not know how to log out once I logged in ! I have to close my browser to disconnect, but instead I would like to have a "disconnect" page which would redirect towards the "login" page (this one with the hideous form used to log in...).
Does anyone has an idea ?
Thanks per advance
PS : I apologize for my pathetic English :)
Express' basicAuth uses HTTP Basic Authentication whose implementation doesn't need HTML pages, cookies nor session ids. Its main drawbacks are its not secure and, our concern here, there is no mechanism in the spec for the server to instruct the browser to log out.
express.basicAuth() calls require(blah-blah/connect/lib/utils).unauthorized() which sends a 401 status with header 'WWW-Authenticate: Basic realm="..."'. The browser handles the authentication window and from then on sends a header like 'Authorization: Basic YmFzaWM6YmFzaWM=' which contains the username and password.
(express.basicAuth is not secure, especially over HTTP, because you can get the username:password with
new Buffer('YmFzaWM6YmFzaWM=' , 'base64').toString()
which gives basic:basic in this case.)
Our problem is the HTTP spec does not provide a way to stop that header being sent. A workaround for HTTPS is to redirect the user to a URL on the same domain having incorrect credentials.
The HTTP workaround I use for Express V3 can be used with app.use(express.basicAuth(...)). I find this easier to use than other solutions which require a call to middleware in every secured route, e.g. app.get('/secure', checkAuth, function (req, res) {...}).
Here is a working example.
var express = require('express'),
http = require('http'),
app = express();
app.use(express.favicon()); // prevent interference during test
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({ secret: 'winter is coming' }));
app.use(function (req, res, next) {
if (!req.session.authStatus || 'loggedOut' === req.session.authStatus) {
req.session.authStatus = 'loggingIn';
// cause Express to issue 401 status so browser asks for authentication
req.user = false;
req.remoteUser = false;
if (req.headers && req.headers.authorization) { delete req.headers.authorization; }
}
next();
});
app.use(express.basicAuth(function(user, pass, callback) {
callback(null, user === 'basic' && pass === 'basic');
}, '***** Enter user= basic & password= basic'));
app.use(function (req, res, next) {
req.session.authStatus = 'loggedIn';
next();
});
app.use(app.router);
app.get('/secure', function (req, res) {
res.send([
'You are on a secured page.',
'<br>',
'Refresh this page without having to log in again.',
'<br/>',
'Log out.'
].join(''));
});
app.get('/logout', function (req, res) {
delete req.session.authStatus;
res.send([
'You are now logged out.',
'<br/>',
'Return to the secure page. You will have to log in again.',
].join(''));
});
http.createServer(app).listen(3000, function(){
console.log('Express server listening on port 3000. Point browser to route /secure');
});
P.S. Your English is excellent.
For express.js 4 and basicAuth, you can use this method:
app.get('/logout', function (req, res) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.sendStatus(401);
});
Adding to wyllman, the 401 code is Unauthorized.
You can simply respond with res.status(401).send('Logged out')
or
app.get('/logout', function (req, res) {
res.status(401).send('Logged out')
//or res.status(401).end() if you don't want to send any message
});
I've confirmed that redirecting the user to a /logout page with an http code 401 and html with <a> element links to /login works.
Related
I have a google auth API in my app, can I somehow attach user data with redirect function? Or how can I see if the user logged in at my react app.
For example here I'm sending username in my request field, but I don't know the way to access it...
router.get('/google/redirect', passport.authenticate('google'), (req, res) => {
res.redirect('http://localhost:3100/login1/' + req.user.username);
});
Thanks in advance!
you have to request the authentication flow alike this:
router.get('/auth/login',
(req, res, next) => {
// Save the url of the user's current page so the app can redirect back to it after authorization
if (req.query.return) {req.session.oauth2return = req.query.return;}
next();
},
// Start OAuth 2 flow using Passport.js
passport.authenticate('google', { scope: ['email', 'profile'] })
);
and then handle the post-back alike this:
// OAuth 2 callback url.
// Use this url to configure your OAuth client in the Google Developers console.
router.get('/auth/google/callback',
// Finish OAuth 2 flow using Passport.js
passport.authenticate('google'), (req, res) => {
// Redirect back to the original page, if any
const redirect = req.session.oauth2return || '/';
delete req.session.oauth2return;
res.redirect(redirect);
}
);
while your localhost does not have any route-able IP, therefore you will never receive any post-back. hosting the script on-line (with a publicly route-able IP) is required to make it work "as expected".
I'm creating a simple PWA to draw in multiple data sources into one application. I'm currently trying to set up authentication using a combination of passport and the twitter-strategy.
After the user has successfully authenticated they're account, twitter redirects to the callback endpoint, with the valid user data.... essentially the auth has been successful. However, when sending the user back to the client side application a html document with the word null is presented, rather than the application.
With the following code, I expect:
Twitter to return to callback URL
Server to perform actions in authSuccess function
Redirect to the provided url on the client
routes.js to server the application shell via the catch all route
Client application to boot and handle the URL served
Currently, only the first two steps are successful, and the app simply displays null (with the correct url in the address bar), rather than the expected output. Changing the location of the writeHead() call to / works, and the user is authenticated as expected ().
routes.js
let users = require('../controllers/userController');
app.get('/user/auth/twitter/callback',
passport.authenticate('twitter', {
failWithError: true
}),
function(req, res) {
console.log('[Twitter] Auth success route hit');
users.authSuccess(req, res);
},
function(err, req, res, next) {
console.log('[Twitter] Auth failure route hit');
users.authFailure(err, req, res, next);
}
);
app.get('*', function(req, res){
console.log('[Route] Catch All: ' + req.path);
res.sendFile(path.resolve(__dirname, '../../public/index.html'));
});
userController.js
authSuccess(req, res) {
console.log('[User Controller] User',req.user);
// Set some cookies here
res.writeHead(302, {'Location': '/user/profile'});
// res.redirect('/user/profile');
res.end();
}
Any help much appreciated. If you need more code, just ask :)
Thanks
I have the following program to log in with Google:
app.get('/oauth/google', function(req, res) {
res.redirect(<OAUTH2_URL>);
});
app.get('/oauth/google/callback', function(req, res, next) {
var code = req.query.code;
if(!code || !_.isString(code)) {
return next(new Error(400, 'Invalid code'));
}
.
.
.
// I try the code to see if it is valid.
});
How do I only allow Googles redirect back to the application to have access to the callback route, and block regular users from using it?
If you're using sessions then you could set a flag from your /oauth/google path before you redirect off to Google, and then on your /oauth/google/callback simply check for that flag, and reset.
app.get('/oauth/google', function(req, res) {
req.session.authFlag = true;
res.redirect(<OAUTH2_URL>);
});
app.get('/oauth/google/callback', function(req, res, next) {
if (!req.session.authFlag) return next(new Error(403, 'Forbidden'));
else req.session.authFlag = false;
...
});
If you're not using sessions though, or for some reason sessions aren't available because the client doesn't support cookies (which should be a concern in above mentioned solution as well!), then I guess your best bet is to just check for req.query.code because other than that query string (req.query.code) there's no difference between requests redirected by Google and direct requests made by regular user.
(...req.headers.referer/origin could've worked in theory but they're unreliable and shouldn't be used as a measure)
I am using Angular for client and Nodejs (Express) for server side to build single page application. To support browser history for different view, I am using $routeProvider. It works well if I don't refresh the browser. But whenever I refresh the browser I notice that URL is changed which caused issue as that URL pattern doesn't exist at server. Following are more details.
Angular js router code:
$routeProvider.
when('/categoryview', {
templateUrl: 'templates/partials/app/categoryview/CategoryView.html',
controller: 'ApplicationController'
}).
when('/:categoryId/themes', {
templateUrl: 'templates/partials/app/themeview/ThemeView.html',
controller: 'ThemeViewController'
})
.otherwise({redirectTo: '/categoryview'});
URL in browser as application launched first time: http://localhost:3000/themelibrary#/categoryview
URL in browser on refresh: http://localhost:3000/categoryview#/categoryview
If you notice then you will found that the root URL "/themelibrary" is changed into "/categoryview" which caused issue as "/categoryview" is not supported by server. I also tried different version of Angularjs but not success.
Please help and let me know if need more code to explain this problem.
Edit: Added Nodejs router details
UI Routes:
module.exports = function(app, passport) {
// route for home page
app.get('/', function(req, res) {
res.redirect('/login');
});
//Route for login to present the login page back to user
app.get('/login', function(req, res) {
res.set({'content-type': 'text/html; charset=utf-8'})
res.render('login.ejs', {message: req.flash('loginMessage')})
});
//Route to get the username and password and authenticate
app.post('/authenticate', passport.authenticate('local-login', {
successRedirect: '/themelibrary', // redirect to the secure themelibrary section
failureRedirect: '/login', // redirect back to the signup page if there is an error
failureFlash: true // allow flash messages
}));
// route for default lending page
app.get('/themelibrary', isLoggedIn, function(req, res) {
var url= require('url');
console.log("themelibrary hash url >> " + url.hash);
res.charset = 'utf8';
res.set({'content-type': 'text/html; charset=utf-8'})
res.render('index.ejs', {
user: req.user
// get the user out of session and pass to template
});
});
// route middleware to make sure a user is logged in
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/');
}
API Routes:
module.exports = function(app) {
app.get('/users/:id', userService.getUserById);
app.get('/users', userService.getAllUsers);
app.post('/themes', themeService.addTheme);
app.get('/themes/:id', themeService.getThemeById);
app.put('/themes/:id', themeService.updateTheme);
app.delete('/themes/:id', themeService.deleteTheme);
app.get('/themes', themeService.getThemes);
app.get('/resources/:code', languageResourceService.getLanguageResourceByCode);
app.get('/config', applicationConfigService.getApplicationConfig);
app.post('/keepalive', applicationConfigService.keepAlive);
app.get('/categories', categoryService.getAllCategories);
};
I would need to see your express routing rules to give a full answer, but since themelibrary is not a valid route according to the $routeProvider, your otherwise rule is being activated if you browse to this from within your application.
If you refresh the browser at this URL, $routeProvider is bypassed and the request is sent directly to your node.js instance where express will handle the route.
As for the hash route, that all depends if you have html5mode enabled or not as #david-spence pointed out.
Edit
The penny finally dropped on this one. If your angular app has loaded (and the only way for it to load is via the /themelibrary path), it will take over all of your routing, so any route changes via the application will be handled by ngRoute. That is to say, there will be no page reloads. Therefore, if the backend server is being hit by a request to /categoryview, some other part of the application is requesting a full page redirect via window.location.href or something similar.
In short, your routes are fine, some other part of the application is bypassing your routes and going straight back to the server (which it shouldn't).
Users visiting http://localhost/login are instantly redirected to Facebook for confirmation of application usage. Once authorized, Facebook contacts http://localhost/login?code= with a special code that allows the server to obtain the user's information such as their name and gender.
Express().get('/login', Passport().authenticate('facebook', {
failureRedirect: 'http://localhost/',
}), function(req, res) {
database.saveData(req.user, **randomlyGeneratedHash**);
res.cookie('session', **randomlyGeneratedHash**);
res.end();
});
This works as expected, but when authenticated users visit the /login in succession, the whole process is repeated and they get a new cookie.
Is there a way that I can run some code inbetween Express and Passport, to stop Passport from redirecting to Facebook if the user has a valid cookie already?
You can use something similar to ensureAuthenticated on your /login route:
var CheckIfAlreadyLoggedIn = function(req, res, next) {
if (req.isAuthenticated()) {
return res.redirect('/'); // or where you want them to go
}
next();
};
Express().get('/login', CheckIfAlreadyLoggedIn, Passport().authenticate('facebook', ...));
This would redirect users that are already logged in back to / when they try to access /login.