Block regular users from Google auth callback Express Route - node.js

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)

Related

Page displays 'null' after authentication

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

express response locals disappear

I would like to send error messages back to the client without adding them to the url. Here is my attempt:
exports.register = function(req, res) {
if (req.body.password != req.body.password_repeat) {
res.locals.err = 'Passwords must match.';
res.locals.action = 'register';
res.redirect('/');
return;
}
...
exports.index = function(req, res) {
req.url = '/';
res.render('index', {
action: res.locals.action,
error: res.locals.error,
redirect: res.locals.redirect
});
};
So the redirect works fine and exports.index executes. The problem is that res.locals are gone by then. Is this because once I redirect it is considered a new req/res cycle? Any way I can pass this information through redirect without doing something like res.redirect('/?error=error')
You can use flash package from expressjs, but you need to have session middleware to use it. Also, you can use express-flash package from RGBboy but you need to have both cookieParser and session middlewares in this case.

How to log out from basicAuth (Express)

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.

Issue with Express and middleware

function redit (req, res, next) {
var session = req.session.user;
if (session) {
res.redirect('/home');
next();
}
else {
res.redirect('/');
next();
}
}
app.get('/', redit, function (req, res){
res.render('home0.ejs');
});
I code this middleware to check if there's a req.session.user, if there is, the user would be redirected to home, if not, would be redirected to /. But when this middleware is called, Chrome says to me Error 310 (net::ERR_TOO_MANY_REDIRECTS)', any solutions...?
You miss the fact that after redirect an anonymous user (with falsy req.session.user value) will end up at the same (/) page - so their identity will be checked up by redir middleware again... and again... and again. Hence the 'TOO MANY REDIRECTS' error.
The common solution is to redirect all the anonymouses to some other gateway page - and that page obviously should NOT check session.user.

Custom returnUrl on Node.js Passport's Google strategy

I'm using Express and Passport OpenID Google strategy and I would like to set returnURL on each auth request to be able to return to the page that initiated that auth.
The situation is that I have HTML5 slides application with Node.js backend (and with social stuff and editor and Portal and extensions... https://github.com/bubersson/humla) and I want be able to log in user on some slide (via slide menu...) but then I want him to get back to same slide easily.
So I would need something like this?
app.get('/auth/google', function(req,res) {
var cust = "http://localhost:1338/"+req.params.xxx;
passport.authenticate('google', returnURL:cust, function ...
}
I've read Passport's guide, but still don't know how to do that. I know this wouldn't be safe, but how else could I do it?
Or how can I make the application to return to the page from where the login has been initiated? Or is there a way to make OpenID authentication using AJAX (and still be able to use passport as well)?
I've figured this out for my apps Twitter authentication, I am sure that the GoogleStrategy is quite similar. Try a variant of this:
Assuming you have defined the route for the callback from the authentication service like so (from the passport guide):
app.get('/auth/twitter/callback',
passport.authenticate('twitter', {
successRedirect: authenticationRedirect(req, '/account')
, failureRedirect: '/'
})
);
Just change that block to this:
app.get('/auth/twitter/callback', function(req, res, next){
passport.authenticate('twitter', function(err, user, info){
// This is the default destination upon successful login.
var redirectUrl = '/account';
if (err) { return next(err); }
if (!user) { return res.redirect('/'); }
// If we have previously stored a redirectUrl, use that,
// otherwise, use the default.
if (req.session.redirectUrl) {
redirectUrl = req.session.redirectUrl;
req.session.redirectUrl = null;
}
req.logIn(user, function(err){
if (err) { return next(err); }
});
res.redirect(redirectUrl);
})(req, res, next);
});
Now, define your middleware for authenticated routes to store the original URL in the session like this:
ensureAuthenticated = function (req, res, next) {
if (req.isAuthenticated()) { return next(); }
// If the user is not authenticated, then we will start the authentication
// process. Before we do, let's store this originally requested URL in the
// session so we know where to return the user later.
req.session.redirectUrl = req.url;
// Resume normal authentication...
logger.info('User is not authenticated.');
req.flash("warn", "You must be logged-in to do that.");
res.redirect('/');
}
Works!
Wherever you have your login button, append the request's current URL as a
query parameter (adjust for whatever templating system you use):
<a href='/auth/google?redirect=<%= req.url %>'>Log In</a>
Then, add middleware to your GET /auth/google handler that stores this value in
req.session:
app.get('/auth/google', function(req, res, next) {
req.session.redirect = req.query.redirect;
next();
}, passport.authenticate('google'));
Finally, in your callback handler, redirect to the URL stored in the session:
app.get('/auth/google/callback', passport.authenticate('google',
failureRedirect: '/'
), function (req, res) {
res.redirect(req.session.redirect || '/');
delete req.session.redirect;
});
Try res.redirect('back'); in the callback for passport.authenticate
According to the author this isn't possible with OpenID strategies. We managed to update these dynamically by directly accessing the variables:
app.get('/auth/google', function(req, res, next) {
passport._strategies['google']._relyingParty.returnUrl = 'http://localhost:3000/test';
passport._strategies['google']._relyingParty.realm = 'http://localhost:3000';
passport.authenticate('google')(req, res, next);
});

Resources