ExpressJS middleware not working when using NextJS Link - javascript

I'm using Express routes with Next, on the example below /a should be accessible by authorised people, while /b is public.
... other imports...
const app = next({ isDev })
const handle = app.getRequestHandler()
async function isAuth(req, res, next) {
const token = req.header('x-Auth-Token');
if (!token) return res.status(401).send('Access denied. No token provided.');
req.user = 'Connected!';
next();
}
app.prepare().then(() => {
const server = express()
server.get('/a', isAuth, async (req, res) => {
return app.render(req, res, '/a', req.query)
})
server.get('/b', async (req, res) => {
return app.render(req, res, '/b', req.query)
})
server.all('*', (req, res) => {
return handle(req, res)
})
server.listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
Pretty easy and straightforward, for now I'm correctly getting my access denied on the /a using the url bar of the browser except when I use a <Link href="/a"> from my /b page. Then the page shows the hidden content and my access has not been checked... why? How can I resolve this issue?
This issue can be replicated using this Github link, you will just need to add the isAuth example as I did on the example above.

That is part of how the Next.JS Link works. It already pre-fetches the sources for the upcoming site, without ever fetching against the real endpoint, thus you are required to implement both frontend and backend checks for your current situation.
For further information feel free to follow this discussion within Next.JS Github Issue: Github NextJs Restricted Links. It clearly explains how to deal with such a situation.

Related

Expressjs multiple optional parameters trigger request handler twice

Hello I need a second pair of eyes because I'm not sure why this is happening...
I want to create 1 request handler which might accept 0 or 1 or 2 parameters eg: http://hocalhost:3000/{seed}/{size}, seed and size parameters must be optional.
However the below example doesn't work and the console.log is being printed twice. This doesn't happen if I change the handlers route to /api/:seed?/:size?. Why is this happening & what am I doing wrong?
const sharp = require('sharp');
const express = require('express');
const settings = require('./settings');
const app = express();
const calculateDensity = (s) => {
return (72 * s) / 180;
}
app.get('/:seed?/:size?', (req, res) => {
console.log('Why am I seeing this log?');
res.end();
})
app.listen(settings.PORT, () => {
console.log(`Example app listening at http://localhost:${settings.PORT}`)
})
Browser automatically called favicon.ico after load.
it load twice
for favicon.ico and the route we define.
We can resolve it like below code
app.get('/favicon.ico', (req, res) => {
res.end()
})
app.get('/:seed?/:size', (req, res) => {
console.log(req.url)
if (req.url !== "/favicon.ico") {
console.log('Why am I seeing this log?');
}
res.end();
})
So this is your default route
app.get('/:seed?/:size?', (req, res) => {
console.log('Why am I seeing this log?');
res.end();
})
Lets have another route
app.get('/test', (req, res) => {
console.log('Test called');
res.end();
})
Now on browser localhost:port/test
Now which route will call.
As per your code it will consider that you are calling default route, where your first two argument is optioal.
So always default route will called. and test route skipped. Because test is now parameter of default route. Not another route
Think deeply Either test route work or default route

Passportjs req.logout() not working after moving to separate file

I am using NodeJS with Express as the backend for a dashboard web app. I have started splitting the backend server code into smaller files (each route is in it's own file). I split the login function successfully, however when I split the logout function not only does this break the logout function, but it also breaks the login function.
login.js
module.exports = function (passport) {
const express = require('express'),
router = express.Router();
//Login
router.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
return next(err);
}
if (!user) {
return res.status(400).send([user, 'Cannot log in', info]);
}
req.login(user, err => {
res.send('Logged in');
console.log('User logged in');
});
})(req, res, next);
});
return router;
};
logout.js
module.exports = function () {
const express = require('express'),
router = express.Router();
//Logout
router.get('/logout', function (req, res) {
req.logout();
console.log('Logged out');
return res.send();
});
return router;
};
require/use in index.js
const loginRoute = require('./routes/login.js')(passport);
exprApp.use('/api', loginRoute);
const logoutRoute = require('./routes/logout.js');
exprApp.use('/api', logoutRoute);
login.js works the same as before when logout.js is not split off so I don't think the issue lies there. Maybe it's because the res.send is never actually sent to the frontend somehow, so it never reaches the .then? Does anyone know how to fix this?
Edit: I am using Vue in the frontend. Here is the code for handling the response. It never seems to reach the callback, and therefore router.push, etc. are not executed it just hangs.
logOut: ({commit}) => {
axios.get("/api/logout")
.then(() => {
router.push("/");
commit('RESET_USER');
commit('RESET_DEALERSHIPS');
commit('RESET_LEADS');
})
.catch((errors) => {
console.log(errors);
});
}
The 'Logged out' text from logout.js is logged to the browser's console, so the endpoint must exist and be reachable by the frontend (and no errors are logged).
I fixed this issue by simply changing the line
exprApp.use('/api/logout', logoutRoute);
To
exprApp.use('/api/logout', logoutRoute());
I don't know why this works but it solves the issue. As a side note, if I change loginRoute to also use the brackets I get an error from Node.

passport and serving files nodejs

i am using passport with google strategy for authentication
my folder structure:
views
home.html
enter.html (this has just one google+ button)
app.js
routes
auth.js (for google login)
i want the client to be directed to enter.html and not be able to use home.html if req.user is not set ( req.user is set when user is authenticated using google )
once authentication is done user should be redirected to home.html
app.use(express.static()) makes both of them available which is not what i want
the google login page comes by auth/google
and i also need to know what i should keep as the callback uri
in app.js
i have done mongodb configuration
i have done passport configuration
what to do next?
in auth.js
const router = require('express').Router();
const passport = require('passport');
router.route('/google')
.get(passport.authenticate('google', { scope: ["profile"] }));
router.route('/google/redirect')
.get(passport.authenticate('google'), (req, res, next) => {
// res.redirect what
});
module.exports = router;
To serve the home.html page you could redirect to a protected home route. Here an example of how I would go about implementing this.
auth.js
router.route('/google/redirect')
.get(passport.authenticate('google', { failureRedirect: '/' }), (req, res, next) => {
// Set to redirect to your home route / html page
res.redirect('/home')
});
To prevent users from going to home without authorization, you should also add a route guard to your /home route.
routes.js
const { checkAuth } = require('./guards'); // Protected routes
router.get('/home', checkAuth, async (req, res) => {
res.render('home')
});
guards.js
module.exports = {
checkAuth(req, res, next) {
if (req.isAuthenticated()) {
return next()
} else {
res.redirect('/')
}
},
}

When fixing react-routing for production, fetch cannot find api url

I have a react front-end with an express back end. The express just serves the static build files from the react side in production. I was having a problem with React routing working in production, as many have had, so I fixed it as so:
server.js:
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'client', 'build', 'index.html'));
});
app.get('/api/customers', (req, res) => {
const customers = [
{id: 2, firstName: 'Test', lastName: 'Case'},
{id: 3, firstName: 'Foo', lastName: 'Bar'},
];
res.json(customers);
});
The '/*' solve the routing problem in production, but now the fetch for the '/api/customers' does not work anymore.
customer.js:
componentDidMount() {
fetch('/api/customers')
.then(res => res.json())
.then(customers => this.setState({customers}, () => console.log('Customers fetched...', customers)))
.catch(() => console.log('Error'));
}
This fetch request logs 'Error' when ran. It seems as though the url for the api is changing somehow as a result of the '/*' change in server.js, but I am not sure how I should change the fetch argument to make it work. The fetch was working using just the '/':
server.js:
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'client', 'build', 'index.html'));
});
However, this obviously stops react routing from working in production. What do I need to change my fetch argument to in order to make this work?
Change the order of the routes in server.js:
app.get('/api/customers', () => {});
app.get('/*', () => {});
The routes in express are first come first served, and as "/api/customers" matches "/*", it will return your index.html if your list them the other way around.
thank you so much for this solutiion #David Filipidisz !! The order DOES affect the routes. this is my working code
server.js
...ipmorts here ...
app.use('/users', require('./users/users.controller'));
app.use('/machineLearning', require('./machineLearning/machineLearning.controller'));
app.use('/public', require('./public/public.controller'));
//for Prod
app.use(express.static(path.join(__dirname,'../L10-ui/dist')));
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, '../L10-ui/dist', 'index.html'));
});```

Trouble with authentication

I´m facing a confusion issue while implementing the authentication for my restful api using passport local strategy.
Note:
I got the authentication working successfully when I´m doing it all in my index.js. But I want to use in Classes for better Code separation.
I have a passport.js Module
// config/passport.js
// load all the things we need
var LocalStrategy = require('passport-local').Strategy;
// load up the user model
var mysql = require('mysql');
var dbconfig = require('./database');
var connection = mysql.createConnection(dbconfig.connection);
module.exports = function(passport) {
// passport needs ability to serialize and unserialize users out of session
passport.serializeUser(function (user, done) {
//console.log("SER");
console.log(user),
done(null, user);
});
passport.deserializeUser(function (user, done) {
console.log("XXXX");
console.log(user);
connection.query("SELECT * FROM users WHERE name = ? ",user.name, function(err, rows){
console.log("DER");
console.log(rows);
done(err, rows[0]);
});
});
// passport local strategy for local-login, local refers to this app
passport.use('local-login', new LocalStrategy(
function (username, password, done) {
console.log("hhh");
console.log(username);
connection.query("SELECT * FROM users WHERE name = ? ",username, function(err, rows){
console.log(rows);
return done(err, rows[0]);
});
})
);
// route middleware to ensure user is logged in
function isLoggedIn(req, res, next) {
if (req.isAuthenticated())
return next();
res.sendStatus(401);
}
};
This is my Controller Class:
class AuthenticateController {
constructor(router, passport) {
this.router = router;
this.registerRoutes();
this.passport = passport;
}
registerRoutes() {
this.router.post('/login/:username/:password', this.login.bind(this));
//this.router.get('/logout', this.logout.bind(this));
this.router.get('/content', this.content.bind(this));
}
login(req, res) {
this.passport.authenticate("local-login", { failureRedirect: "/login"}),
res.redirect("/content");
}
content(req, res ) {
console.log(req.user);
if (req.isAuthenticated()) {
res.send("Congratulations! you've successfully logged in.")
} else {
res.sendStatus(401);
}
}
isLoggedIn(req, res, next) {
console.log(req.user);
if (req.isAuthenticated())
return next();
res.sendStatus(401);
}
}
module.exports = AuthenticateController;
The Controller gets the router and passport fully configured as parameters from my index.js.
//index.js
var express = require('express')
, cors = require('cors')
, app = express()
, passport = require('passport')
, morgan = require('morgan');
require('./config/passport')(passport); // pass passport for configuration
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(require('express-session')({secret: 'vidyapathaisalwaysrunning',
resave: true,
saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
var apiRouter = express.Router();
app.use('/api', apiRouter);
//
var apiV1 = express.Router();
apiRouter.use('/v1', apiV1);
var authenticateApiV1 = express.Router();
apiV1.use('/auth', authenticateApiV1);
var AuthenticateController = require('./controllers/authenticate');
var ac = new AuthenticateController(authenticateApiV1, passport); //pass in our fully configured passport
//If I call this /login instead of the /auth/login/ in the Controller Class it works!
//app.post("/login",
// passport.authenticate("local-login", { failureRedirect: "/login"}),
// function (req, res) {
// res.redirect("/content");
// });
What is working and what is not working
The Authentication in general is working. In my posted index.js you see app.post("/login", .... If I call this one the authentication is successfully and if I try to reach the restricted content in /auth/content/ req.user has a value (the user object) and I can successfully call req.isAuthenticated() .
BUT, If I use the authentication from /auth/login/username/password the req.user is undefined when trying to reach the restricted Content.
I get no error and the response of /auth/login/username/password/ HTTP Code 301 - 'redirecting to /content.
I have currently no idea what I´m doing wrong here and I´m pretty new to the topic of Node/express/ passport ..
Hope someone has an Idea. If you need something else to help me, just mention it in the comments and I will do my best to provide you everything you need.
Thanks
EDIT:
I recently tried to read the req.user in the login function and even there it is undefined
login(req, res) {
this.passport.authenticate("local-login", { failureRedirect: "/login"}),
console.log(req.user) //undefined
res.redirect("/content");
}
I guess it could be some async problem and I should use some callback functions, but I don´t know how to apply this in my login()
EDIT 2:
Another Issue I´m facing is the integration of the isLoggedIn() request.
If I do this:
registerRoutes() {
this.router.get('/', this.isLoggedIn, this.getUsers.bind(this));
this.router.get('/:id', this.getSingleUser.bind(this));
}
it results in 401 - Unauthorized
A console.log(req.user); in the isLoggedIn() results in undefined.
But if I call the first route without calling isLoggedIn() and do console.log(req.user); the user object exists.
The correct use of callback with passport authentication for local strategy can be as below:
function(req, res, next){
passport.authenticate('local-login', function(err, user, info){
if(err)
return logger.log('error', err);
if(user)
req.login(user, function(err){
if(err) return next(err);
return res.json({'success': true});
});
if(!user)
return res.json({'error':true, 'message': info.message, 'type': info.type});
})(req, res, next);
}
Please note the use of req.login() to explicitly set user in session.
The only thing I'm finding strange is that your route declarations are different.
In the AuthenticateController the route is declared as:
this.router.post('/login/:username/:password', ...
While in index.js, the route is simply declared as
app.post("/login", ...
How is your client submitting the login credentials to the server? If it is by form, like the tutorial, could it be that having :username and :password declared as route params but being sent by form messes with passport?
Try registering the route exactly like index.js
this.router.post('/login', ...
EDIT:
I've found another dicrepancy. In AuthenticateController the res.redirect("/content"); is not wrapped inside a callback. So it is being executed before Authenticate finishes running.
In the index.js example, passport is being used as a route middleware:
app.post("/login",
passport.authenticate("local-login", { failureRedirect: "/login"}),
function (req, res) {
res.redirect("/content");
});
While in the passport.js it is inside the callback. Consider declaring it in the route:
registerRoutes() {
this.router.post('/login', this.passport.authenticate("local-login", { failureRedirect: "/login"}), this.login.bind(this));
(...)
}
login(req, res) {
res.redirect("/content");
}
O, better yet, why not use passport's option to declare both success and failure redirects, since that seems to be all that you are doing:
login(req, res) {
this.passport.authenticate("local-login", { successRedirect: "/content", failureRedirect: "/login" });
}
You are passing this.login.bind(this) as a middleware to this.router.post('/login/:username/:password', this.login.bind(this)); but login(req, res) only responds to the request with res.redirect("/content"); i.e. redirecting to /content
So like you said, you need to supply a callback that does something with the user that is returned from passports middleware verify callback.
app.post("/login",
passport.authenticate("local-login", { failureRedirect: "/login"}),
function (req, res) {
console.log(req.user); // log user in console
res.json({user: req.user}); // send user as json response
});
The custom callback mentioned by #divsingh is if you want to explicitly have control of setting the session, error messages and redirecting the request. Any other information can be found under http://passportjs.org/docs

Categories

Resources