I use node.js, passport and jwt bearer token to protect my routes. What I don't have yet is rate limiting and blocking of ip/user if too many false attempts. What's the best way to implement it for my setup?
I want to give it a try with rate-limiter-flexible. But how can I integrate e.g. the Login Example from here: https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#login-endpoint-protection in my setup below?
helpers/rateLimiter.js
const express = require('express');
const redis = require('redis');
const { RateLimiterRedis } = require('rate-limiter-flexible');
/* What goes here? Example https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#login-endpoint-protection doesn't seem to apply */
Those are my routes:
routes/index.js
const express = require('express');
const router = require('express-promise-router')();
const passport = require('passport');
const passLogin = passport.authenticate('local-login', { session: false, failWithError: true });
const { rateLimiter } = require('../helpers/rateLimiter');
...
router.route('/v1/login')
.post( rateLimiter, passLogin, function(err, req, res, next) {
return res.status(401).send({ status: 401, success: false })
}, controller.login );
router.route('/v1/abc/search')
.post( passJWT_ABC, function(err, req, res, next) {
return res.status(401).send({ status: 401, success: false })
}, controller.search );
You should export middleware in this case.
const express = require('express');
const redis = require('redis');
const { RateLimiterRedis } = require('rate-limiter-flexible');
async function loginRoute(req, res) {
// code from example https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#login-endpoint-protection
}
export default async (req, res, next) => {
try {
await loginRoute(req, res);
next();
} catch (err) {
res.status(500).end();
}
}
And then you should take care of how authorise(), user.isLoggedIn and user.exists checks work with your application login approach.
There is an example with passport-local, should be useful for you as well https://github.com/passport/express-4.x-local-example/blob/67e0f735fc6d2088d7aa9b8c4eb25bc0052653ec/server-secure.js
Related
I have some routes on my API. And have a middleware. Its creating bearer token and checking it. But i want some of my routes dont enter to that middleware so i can access without token. How can i make it ? My middleware :
app.use(async (req, res, next) => {
if (
req.path === "/api/v1/collection/byhome" || // I dont want that part.
req.path === "/api/v1/user/login" // Its working but its not looks like best solution.
) {
next();
} else {
const bearerHeader = req.header("authorization");
if (typeof bearerHeader !== "undefined") {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
req.token = bearerToken;
jwt.verify(req.token, process.env.SECRETKEY, async (err, authData) => {
if (err) {
res.sendStatus(401);
} else {
next();
}
});
} else {
res.statusCode = 400;
const Response = {
message: "Invalid Token",
StatusCode: res.statusCode,
};
res.json(Response);
}
}
});
My Route :
app.get(
`/api/${version}/article/bycollection/:id`,
ArticleRoute.getbycollection
);
your way of doing it is correct you can make your code more readable by making an array of all the middleware you want to be out of scope of your middleware
const whiteListEndpoints = ["/api/v1/this", "/api/v1/this1", "/api/v1/this2"]
then
// your middleware
app.use((req, res,next) => {
//if the path was in the whitelist just call next function
if(whiteListEndpoints.includes(req.url)) return next()
// let the middlware do it's job
})
or you can change your express use order
const firstRoute = app.use("/no_middleware", router);
app.use((req, res, next) => {}) // your middleware
const secondRoute = app.use("/with_middleware", router);
here the first router won't use the middleware since it has not yet been called.
You can use Express.Router to achieve the desired result. With express router you can differentiate between routes and have different middlewares for each router.
Follow the steps given below:
Create a auth middleware middlewares/private.authenticate.js
function auth(req, res, next) {
// do auth stuff...
next();
}
Create a file routes/private/index.js
// private route handler
import { Router } from "express";
import auth from "./middlewares/private.authenticate.js";
const router = Router();
router.use(auth); // use auth middleware
router.route("/")
.get()
.put()
export default router;
Create a file routes/public/index.js
import { Router } from "express";
const router = Router();
router.route("/")
.get()
.put()
export default router;
Your express app file
import express from "express";
const app = express();
import PublicRoutes from "./routes/public";
import PrivateRoutes from "./routes/private";
// public routes path
app.use("/api/public", PublicRoutes);
// private routes path
app.use("/api/private", PrivateRoutes);
You can create a route express.Router() and set this to a path, this router have all auth, then create a second express.Router() and this without auth.
var router = express.Router();
// your code for API auth...
router.get('/api/v1/collection/byhome',myMiddleware, (req, res, next) => {
res.send('Hey There');
})
app.use('/api', router);
var routerGuest = express.Router();
//
routerGuest.get('/', (req, res, next) => {
res.send('Hey There');
})
app.use('/guest', routerGuest)
for authentication, I recomend to make a separate middleware and then pass it to our route
function myMiddleware(req, res, next){
const bearerHeader = req.header("authorization");
if (typeof bearerHeader !== "undefined") {
const bearer = bearerHeader.split(" ");
const bearerToken = bearer[1];
req.token = bearerToken;
jwt.verify(req.token, process.env.SECRETKEY, async (err, authData) => {
if (err) {
res.sendStatus(401);
} else {
next();
}
});
} else {
res.statusCode = 400;
const Response = {
message: "Invalid Token",
StatusCode: res.statusCode,
};
res.json(Response);
}
}
}
I think with this you may have some idea to do it :)
When I make a post request to the /login endpoint in postman it works fine and returns all the information. However when I try to navigate to the end point in the url the route returns unfound. In the console I get GET http://localhost:5000/login 404 (Not Found). Why is the console returning for a get request? If I try to call the post request in axios I get xhr.js:177 POST http://localhost:3000/login 404 (Not Found).
app.js
require("dotenv").config();
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const router = express.Router();
const cors = require('cors');
const app = express();
app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
const mongoose = require('mongoose');
const connection = "password"
mongoose.connect(connection, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
})
const clientRoutes = require('./routes/clientRoutes');
const traderRoutes = require('./routes/traderRoutes');
const loginRoute = require('./routes/loginRoute')
app.use('/', clientRoutes, traderRoutes, loginRoute);
// setup a friendly greeting for the root route
app.get('/', (req, res) => {
res.json({
message: 'Welcome to the REST API for Pave!',
});
});
// send 404 if no other route matched
app.use((req, res) => {
res.status(404).json({
message: 'Route Not Found',
});
});
// setup a global error handler
app.use((err, req, res, next) => {
if (enableGlobalErrorLogging) {
console.error(`Global error handler: ${JSON.stringify(err.stack)}`);
}
res.status(err.status || 500).json({
message: err.message,
error: {},
});
});
app.listen(5000, () => console.log('Listening on port 5000!'))
loginRoute.js
require("dotenv").config();
const express = require("express");
const router = express.Router();
const jwt = require("jsonwebtoken");
const bcryptjs = require("bcryptjs");
const Client = require("../models/clientSchema");
const Trader = require("../models/traderSchema");
function asyncHandler(callback) {
return async (req, res, next) => {
try {
await callback(req, res, next);
} catch (error) {
next(error);
console.log(error);
}
};
}
router.post('/login', asyncHandler(async (req, res, next) => {
let user = req.body;
const trader = await Trader.findOne({ emailAddress: req.body.emailAddress })
if (user && trader) {
console.log(trader)
let traderAuthenticated = await bcryptjs.compareSync(user.password, trader.password);
console.log(traderAuthenticated)
if (traderAuthenticated) {
console.log('Trader match')
const accessToken = jwt.sign(trader.toJSON(), process.env.ACCESS_TOKEN_SECRET)
res.location('/trader');
res.json({
trader: trader,
accessToken: accessToken
}).end();
} else {
res.status(403).send({ error: 'Login failed: Please try again'}).end();
}
} else if (user && !trader) {
const client = await Client.findOne({emailAddress: req.body.emailAddress})
console.log(client)
let clientAuthenticated = await bcryptjs.compareSync(user.password, client.password);
console.log(clientAuthenticated)
if (clientAuthenticated) {
console.log('Client match')
const accessToken = jwt.sign(client.toJSON(), process.env.ACCESS_TOKEN_SECRET)
res.location('/client');
res.json({
client: client,
accessToken: accessToken
});
} else {
res.status(403).send({ error: 'Login failed: Please try again'}).end();
}
} else {
res.status(403).send({ error: 'Login failed: Please try again'}).end();
}
})
);
module.exports = router;
You set POSTMAN to make a POST request, right? When you enter a url in the browser, that causes a GET request - and you have no route to manage this that I can see, but for the default Not found.
you are calling with axios with wrong port no. it should, POST method http://localhost:5000/login as your application is running on port 5000.
but you are calling, POST http://localhost:3000/login
So I'm working with next.js and a custom server (express.js). I have some middlewares, (f.ex. const attachUser) which I'd like to use in my API endpoints. But for some reason, I am unable to use app.use.
The following code only works, when I don't use app.use(attachUser) and add attachUser to every endpoint manually.
require("dotenv").config();
const express = require("express");
const next = require("next");
const bodyParser = require("body-parser");
const cors = require("cors");
//next.js configuration
const dev = process.env.NODE_DEV !== "production";
const nextApp = next({
dev
});
const port = 3000;
const handle = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cors());
const attachUser = (req, res, next) => {
const token = req.cookies["token"];
if (!token) {
return res.status(401).json({
message: "Authentication invalid"
});
}
const decodedToken = jwtDecode(token);
if (!decodedToken) {
return res.status(401).json({
message: "There was a problem authorizing the request",
});
} else {
req.user = decodedToken;
next();
}
};
//app.use(attachUser)
app.get(
"/api/savedItems",
attachUser, //delete when app.use(attachUser) is used
async(req, res) => {
try {
//logic
return res.json(itemData);
} catch (err) {
return res.status(400).json({
error: err
});
}
});
Can someone tell me what I'm doing wrong?
Btw, this is my first project. Any suggestions to improve code are appreciated! Thanks a lot!
You can't write code between a "try {}" and a "catch{}" and aditionally a useless ";" after "try{}". You can use a JS lint tool for checking code
I have been working on this problem for several days but I couldn't find any solution.
For each of my function in the route class I have this 2 Middlewares
index.js
const browser = require('browser-detect');
const detectBrowser = (req,res,next) => {
const result = browser(req.headers['user-agent']);
console.log(result);
if (result) {
if (result.name === 'chrome') {
console.log('Browser: '+result.name+ ' OK!');
next();
} else if (result.name=== 'firefox') {
console.log('Browser: '+result.name+ ' OK!');
next();
} else {
res.render('browser-response');
}
}
};
const redirectHome = (req,res,next) => {
if(req.session.token && req.session.firstName && req.session.lastName) {
if (!req.session.checked) {
res.redirect('/term-of-service');
}
else {
res.redirect('/index');
}
} else {
next();
}
};
and a simple function I want to test is:
router.get('/', detectBrowser, redirectHome, (req, res) => {
res.render("login");
});
I plan to use Mocha and Supertest as frameworks to test the router.get(/) function, but couldn't find anyway to mock the browser and the variables of session (session.token, session.firstName, session.lastName, session.checked).
Does anyone have an idea about this?
Thank you in advance.
For this question I already found out a work around solution.
For the middleware detectBrowser we just need to add
} else if (result.name=== 'node') { next(); }
because we are working in the node enviroment so those lines of code will make the test file pass through the middleware.
For the second middleware we just need to provide stubbing session information for the test. Below is an example :
const route = require ("../../routes/index");
const request = require("supertest");
const express = require("express");
const bodyParser = require('body-parser');
const chai = require('chai');
const expect = chai.expect;
//create app with valid credentials (not yet accept term of service )
let mockingLoggedIn;
//setup mocking session
mockingLoggedIn = express();
mockingLoggedIn.use(express.urlencoded({extended: false}));
mockingLoggedIn.set('view engine', 'ejs');
mockingLoggedIn.use(bodyParser.json());
mockingLoggedIn.use(bodyParser.urlencoded({extended: true}));
// lets stub session middleware
mockingLoggedIn.use(function (req, res, next) {
req.session = {};
next();
});
//provide a fake login
mockingLoggedIn.get('*', function (req, res, next) {
// and provide some accessToken value
req.session.token = "test";
req.session.firstName= 'test';
req.session.lastName = 'test';
req.session.checked = false;
next()
});
mockingLoggedIn.use(route);
And in the test we just need to use that stubbed app to make request to the route. For example:
describe("Testing / route and /login route works", () => {
it('testing index route: GET / should return status 200 and render login site',
(done)=> {
request(mockingLoggedIn) // request an app with stubbed credentials
.get("/")
.expect("Content-Type", "text/html; charset=utf-8")
.expect(200)
.end(function (err,res) {
expect(err).to.be.null;
done();
})
});
});
Hopefully this will help someone :)
I am trying to fix the following error:
/home/ubuntu/workspace/src/util/utils.js:2
if (req.isAuthenticated()) {
^
TypeError: Cannot read property 'isAuthenticated' of undefined
at Object.isLogged (/home/ubuntu/workspace/src/util/utils.js:2:11)
at Object.<anonymous> (/home/ubuntu/workspace/src/routes/index.js:6:23)
I am using passport the following in my app:
require('dotenv').config()
const express = require('express')
//...
const session = require('express-session')
const passport = require('passport')
// configure passport
require('./config/passport')(passport)
const auth = require('./routes/auth')
const index = require('./routes/index')
const app = express()
// ...
app.use(
session({
secret: 'super-mega-hyper-secret',
resave: false,
saveUninitialized: true,
})
)
app.use(passport.initialize())
app.use(passport.session())
app.use((req, res, next) => {
res.locals.currentUser = req.user
next()
})
// routes
app.use('/', auth)
app.use('/', index)
app.listen(port, host, () => {
console.log(`Listening on ${host}:${port}`)
})
module.exports = app
My passport.js file looks like the following:
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const service = require('../service/auth')
module.exports = () => {
passport-serialize-deserialize
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser(async function(id, done) {
const user = await service.findById(id)
done(null, user)
})
passport.use('local', new LocalStrategy({
usernameField: 'username',
}, async(username, password, done) => {
const user = await service.signin(username, password)
done(null, user)
}))
}
When my / gets called I am using the isLogged() function to control if a login is required:
const express = require('express')
const router = express.Router()
const utils = require('../util/utils')
router.get('/', utils.isLogged(), (req, res) => {
res.render('dashboard')
})
module.exports = router
The definition of the function can be found in my utils.js file, where the error log above is pointing:
function isLogged(req, res, next) {
if (req.isAuthenticated()) {
next()
} else {
res.redirect('/')
}
}
module.exports = {
isLogged,
}
The full code flow can be found in the following repo: passport example
Any suggestions what I am doing wrong?`
I appreciate your replies!
When you run this code:
router.get('/', utils.isLogged(), (req, res) => {
res.render('dashboard')
});
What happens is that utils.isLogged() is being executed, the the result of this execution is registered as a middleware.
Since you try to execute it without any parameters passed, res is passed as undefined and you get your error.
Now, what you really want to do is pass the function itself, not it's execution, so when express will call it (during request processing), it will pass the parameters to it. So your code should look like this:
router.get('/', utils.isLogged, (req, res) => {
res.render('dashboard')
});