I'm building an API with Node.js, and I have some endpoints I want to secure.
For simplicity let's assume I'm using HTTP basic authentication (passport-http) for all of my endpoints.
What I'd like to do on top of that, is to make sure that a route like this: api.example.com/users/:uid/ is only accessible by a user with that ID.
I can do it with something like this:
app.get('/users/:uid',
passport.authenticate('basic', {
session: false
}),
function (req, res, next) {
if (req.params.uid !== user.id) {
return next(new Error('Unauthorized'));
}
return next();
},
function (req, res, next) {
// do secret stuff
}
);
But I wonder if there's a way to do this without adding additional middleware, by using Passport itself:
app.get('/users/:uid',
passport.authenticate( ??? ),
function (req, res, next) {
// do secret stuff
}
);
Is it possible? If not, is there a better way?
You can try something perhaps like this. General description: authenticate all requests that hit anything under the /users route as requiring authentication. On your specific route, use some middleware that makes sure that the user trying to access the specific route is the one in the route itself via that uid.
function authorizeUser(req, res, next) {
if (req.user.uid !== req.params.uid) next(new Error('Not your profile!'));
next();
}
// Require login for entire /users section
app.use('/users', passport.authenticate('basic', { session: false }));
// Authorize /users/:uid section to one user
app.use('/users/:uid', authorizeUser);
// Nested routes will all be secured by the middleware above.
app.get('/users/:uid', function (req, res) {
// Secret stuff
});
app.get('/users/:uid/foo/bar', function (req, res) {
// Also secret
});
If you're only securing one endpoint, you can just put it all on the same route.
Related
I'm working on a notes app , where people can keep their notes save (using express).
I want to add google authentication and for that I'm using passport.
My routs are -
/notes/:userId => for home page
http://localhost:8080/login/google/redirect => Authorized redirect URL
I'm also using a middleware isLoggedIn for checking if the user is loged in or not.
My middleware code -
module.exports.isLoggedIn = (req, res, next) => {
if(!req.user ){
req.flash('error', 'User must be signed-In');
return res.redirect('/login');
}
next();
}
In this I'm checking if the req have user property which passport atomatically adds while login using passport.autheticate() .
But now when i'm login using Google I need to use a fixed redirect URL. So how i redirect user to notes/:userId after authentication.
I tried using req.redirect in my redirect URL
router.get("/login/google/redirect", passport.authenticate('google', {failureRedirect: '/register'}),
async (req, res) => {
let userId = req.user._id;
res.redirect(`/notes/${userId}`);
});
but can't able to pass my middleware isLoggedIn.
How can I make this possible ?
Use isLoggedIn as /notes/:id route's middleware.
router.get("/notes/:id" , isLoggedIn, async (req, res) => {
// your logics code
});
Using Nodejs and an Express server I'm trying to prevent anyone from reaching my second set of routes without logging in and while this works I get stuck in a redirect loop if the session doesn't detect the email in the session token. I believe its trying to check for the req.session.email for the /users endpoint as well causing the redirect loop but as the session checking middleware is used after I thought the /users endpoints would avoid the check.
How can I organize my code so that the books endpoints can only be reached when the req.session.email is satisfied and also not get stuck in a redirect loop when someone tries to reach it without being logged in?
app.use('/users', users)
app.use((req, res, next) => {
if(!req.session.email){
res.redirect('/login')
}
else{
next();
}
})
app.use('/books', books);
The order of the app.use statements is not really important in this case; You could add your middleware to the route-level if you're only checking the /books endpoint.
const yourMiddlewareFunction = (req, res, next) => {
if(!req.session.email){
res.redirect('/login')
}
else{
next();
}
}
app.use('/books', yourMiddlewareFunction, books);
If you only want to protect the /books endpoints, you can do it like this :
function requireLogin(req, res, next) {
if(!req.session.email){
res.redirect('/login')
}
else{
next();
}
}
app.use('/books', requireLogin, books);
I made a custom middleware for Express router that allows me to whitelist certain endpoints of my API to be excluded from authentication. However I have a route where I depend on URL parameter and I can't get my middleware to work as intended with it. Apparently :profileId doesn't do anything and my API endpoint still requires authentication.
The reason I need that path to be excluded from authentication is because of my React frontend that should display that data to the public (without people registering and logging in). Any tips how to solve this?
const apiAuth = (req, res, next) => {
let authRequired = true;
if (
req.path == "/api/users/register" ||
req.path == "/api/users/login" ||
req.path == "/api/profiles/:profileId"
) {
authRequired = false;
}
if (authRequired == true) {
// Auth check logic
}
}
There's a few better approaches for handling the requirement of middleware, that are generally used over the method you're suggesting:
Only include your authentication middleware on routes you require it:
const authenticationMiddleware = (req, res, next) => {
// your login check logic
}
router.get('/api/users/me', authenticationMiddleware, (req, res, next) => {
// your route logic, this endpoint now requires you to be logged in, as you have specified your authentication middleware in the declaration,
})
router.get('/api/profiles/:profileId', (req, res, next) => {
// your route logic, this endpoint does not require you to be logged in as you have not put the middleware in the route delcaration
})
Or, add the authentication middleware based on where your routes are called:
router.get('/api/profiles/:profileId', (req, res, next) => {
// your route logic, this endpoint does not require you to be logged as we have not told our router to use the middleware yet
})
router.use(authenticationMiddleware)
router.get('/api/users/me', (req, res, next) => {
// your route logic, this endpoint now requires you to be logged in, as the router has been told to use the middleware at this point.
})
Why these methods? Try and think of all the router or app calls you're making as adding to a stack which express uses to handle calls to your site or API. As it works its way through looks for routes it will call any middlewares it finds on its way.
This solves the issue of having to declare a list or array of routes which do or don't require a particular piece of authentication, etc.
You'll also need to make sure to call next() in your middleware if you want it to work, as this tells express to continue going through all the routes/middleware's it has.
router.use((req, res, next) => { // export as single route in a file
if (req.isAuthenticated()) {
next();
return;
}
res.sendStatus(401);
});
const authenticate = (req, res, next) => {
if (req.isAuthenticated()) {
next();
return;
}
res.sendStatus(401);
};
The above are 2 ways of writing the authentication route for use in another route (like below). Which way is preferred and why?
router.post('/', authenticate, (req, res, next) => {});
The above way will affect all the requests that your ExpressJS app is
serving, where as the second approach uses Object oriented scripting
way to authenticate only those requests that require authentication.
Say your are writing a sign in or sign up API, you wouldn't need an authentication parameter for that unless mentioned otherwise.
****UPDATE****
The first approach will affect all the requests that your router is serving.
You probably have used the router in your App.js file as
const myRoute = require('./routes/test'); // where `test.js` is a file in routes folder with your code above
app.use('/some_route', myRoute);
All requests going to http://servername:port/some_route/.... will be filtered in your test.js file now.
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);
});