Node express passport (JWT) - callback after auth - javascript

I have authentication working with passport JWT, but I am having difficulty running a callback function/response, only if a user is authenticated. How can this be done?
In my routes:
import express from 'express';
import passport from '../../config/passport';
import memberInfoCtrl from '../controllers/memberInfo';
const router = express.Router();
router.route('/members')
.get(membersCtrl.tokenCheck, passport.authenticate('jwt', { session: false }), membersCtrl.doSomethingElse);
export default router;
I want membersCtrl.doSomethingElse to be run if authentication is successful.
Here is my tokenCheck function:
function tokenCheck(req, res, next) {
const token = getToken(req.headers);
if (token) {
const decoded = jwt.decode(token, config.secret);
User.findOne({
email: decoded.email
}, (err, user) => {
if (err) throw err;
if (!user) {
return res.status(403).send({
success: false, msg: 'Authentication failed. User not found.'
});
}
// res.json({ success: true, msg: 'Welcome to the member area!' });
return next();
});
} else {
return res.status(403).send({ success: false, msg: 'No token provided.' });
}
}
Using res.json in tokenCheck works fine, but the doSomethingElse function is not getting called afterwards.
I thought it's because we send a response like this:
res.json({ success: true, msg: 'Welcome to the member area!' });
Replacing the res.json with return next(); returns the error:
Error: Unknown authentication strategy \"jwt\"
Why does this happen - how can I check for authentication before executing another function, for the same route?
I'm sure it's something silly i'm missing. Any help is appreciated!
Here is part of my main express script that initialises passport:
import passport from 'passport';
...
app.use(passport.initialize());
app.use('/api', routes);
...
Passport config:
const JwtStrategy = require('passport-jwt').Strategy;
import User from '../server/models/user';
import config from './env';
module.exports = (passport) => {
const opts = {};
opts.secretOrKey = config.secret;
passport.use(new JwtStrategy(opts, (jwtPayload, done) => {
User.findOne({ id: jwtPayload.id }, (err, user) => {
if (err) {
return done(err, false);
}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
}));
};

Your general approach is correct. To protect a route depending on some conditions write a custom middleware calling next() if those conditions are fulfilled.
But in your case this is not necessary as this is what passport-jwt does. So assuming that you configured passport and passport-jwt correctly all you need to write is this:
router.route('/members')
.get(passport.authenticate('jwt', { session: false }), membersCtrl.doSomethingElse);
passport-jwt will extract the JWT from the request and verify it against your provided secret or key. Afterwards it will use passport's verify callback to populate req.user (source).
Additionally: Yes after using res.json() a response is sent which is why your passport middleware and anything beyond is not reached in that case.
Regarding your error Error: Unknown authentication strategy \"jwt\": This usually happens if you did not configure your passport authentication correctly. If you include it in your question I will take a look at it and extend my answer accordingly.
Update: Your code looks good to me except one thing: You did not specify the jwtFromRequest attribute in your passport-jwt options which is mandatory. Or did you by any chance forget to invoke your passport config?
Update 2: Further clarification regarding my comment below: 1.) Import your ES6 passport config module (where you added the jwtFromRequest option) in your main express script and invoke it:
import passport from 'passport';
import passportConfig from 'path/to/passport/config';
...
app.use(passport.initialize());
passportConfig(passport);
...
2.) Make sure to remove your tokenCheck function, you don't need it. See the first part of this answer.
Update 3: 'Unauthorized' is great because it means that you are successfully protecting your /members route now. To implement a token-based authentication you now need an additional route /authenticate where users can request access to your web service e.g. by providing credentials (your route will verify these credentials and respond with a JWT which must be signed with the same secret you are using for passport-jwt - otherwise passport-jwt will not be able to verify the token signature later).
But this goes beyond the scope of this question. There should be many resources out there covering this. You can for example implement an /authenticate route using jsonwebtoken which is shown in this article (they are not using passport at all but use jsonwebtoken for token creation and validation).
Note: If you are also implementing a client you have to include some additional logic there to store the JWT and to include it in your requests to the express app.

Related

Why does Swagger endpoint require headers?

So I have added some swagger configuration in a routes folder like so:
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import swaggerDocument from '../swagger/swagger.json';
const router = express.Router();
router.get('/api-docs', swaggerUi.setup(swaggerDocument));
export { router as swaggerRouter }
Now there is an authentication process in the root app.ts file, but my understanding is if I add the swagger endpoint before it executes the authenticate logic, it should not ask for headers:
app.get('/', (req, res) => {
res.send('Howdy!');
});
app.use(swaggerRouter);
app.use(async (req, res, next) => {
const result = await auth.verifyAuth(req).catch((err) => {
return err;
});
if (result.httpCode == 200) {
res.locals.authResult = result
next()
} else {
res.send(result)
}
});
So the authentication logic from where verifyAuth comes from would make for a good middleware to target endpoints instead of the whole entire application, but to refactor it to work as such a middleware is a pain because the author wrote every function to depend on every other function.
And yet, if I add a random endpoint above that authenticate logic like:
router.get('/pingMe', (req, res) => {}
You can go to that one without being asked to provide headers.
What am I missing?
I literally removed the authentication logic and I am still unable to get to the swagger endpoint without being asked for headers.

Can't get Passport.js redirect and flash message to work

I'm a beginner with all things Node and trying to configure a simple passport-local strategy for my app. I was able to create a new user in my database, but beyond that I'm having trouble getting passport.js to work as expected.
Here's my express app structure:
routes
- api
- events.js
- users.js
services
- passport.js
server.js
In server.js I'm configuring the routes individually, like app.use('/api/users', require('./routes/api/users'). Just before that I'm doing the following passport setup:
const session = require('express-session');
const cookieParser = require('cookie-parser');
const passport = require('passport');
const flash = require('connect-flash');
require('./services/passport')(passport);
app.use(session({ secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
My passport.js file looks like this:
const knex = require('../db/knex.js');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
const saltRounds = 12;
module.exports = function(passport) {
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done) {
knex('users')
.where({email: email}).first()
.then(function(user) {
if (user) { return done(null, false, {message: 'User with that email already exists!'}) }
else {
bcrypt.hash(password, saltRounds, function(err, hash) {
var newUser = new Object();
newUser.username = req.body.username;
newUser.email = email;
newUser.password = hash;
knex('users')
.insert(newUser)
.then(done(null, newUser))
.catch(err => { return done(err) })
})
}
})
.catch(err => { return done(err) });
}));
};
And then in users.js I have:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const passport = require('passport');
...
router.post('/signup', passport.authenticate('local-signup', {
successRedirect: '/',
failureRedirect: '/',
failureFlash: true
}));
I'll eventually want to make successRedirect and failureRedirect different from each other, of course, but for now I just want to understand how to make them work. As I mentioned, this code successfully inserted a user into the database. But when I try to create another account with the same credentials, nothing happens. The behavior I'd expect is to see the message "User with that email already exists!" and be redirected to my homepage.
Note that my homepage is at http://localhost:3000, so I'm not sure if setting the redirect to '/' points there or to the current page. The current page is http://localhost:3000/auth, which contains a React component with my signup form, making a POST call to /api/users/signup/. So it's conceivable that '/' would actually look for http://localhost:3000/api/users/signup which as of now doesn't have a GET method. But I'm not getting a 404 or anything; nothing happens, and there's no error. Any advice greatly appreciated.
UPDATE
I just confirmed that removing both redirects causes a 401 response as expected. Also, setting the failureRedirect to /events produces a 404, which is odd because http://localhost:3000/events is definitely configured.
EDIT
I realized it might help to show how I'm making the API call on the client side. Pressing a submit button calls the following signup function:
signup(e) {
e.preventDefault();
axios.post('/api/users/signup', {
username: this.state.username,
email: this.state.email,
password: this.state.password
})
.catch(err => console.log(err));
}
Is your express backend just used as a data API? Because if it is then I'm not sure if the PassportJS redirection would work.
I remember using it in a completely server side rendered app, where the routing was completely on backend, that kind of situation makes it work because any routing is handled on backend and it renders the page then sends it to client.
On a react app, the routing (the actual change in page/url) is handled on frontend, so doing it in Passport like this won't work. I think the best you can do is have error handling in passport that sends some kind of status or message to frontend and then handle it on frontend because that's where the routing is.
For those still facing an issue with sessions resetting when using Passport, I spent hours tracing and debugging only to find out that Passport's authenticate() resets the entire request session internally without so much as a warning. The documentation is just awful, but anyway...
The culprit is the SessionManager part of the framework, as can be seen here
// this line below
req.session.regenerate(function(err) { ... });
Passing the option keepSessionInfo: true to passport.authenticate will preserve the session, i.e:
passport.authenticate('my-strategy', {
... ,
keepSessionInfo: true
})(req, res, next);

Using Express.JS middleware on only some routes

As an exercise in learning NodeJS, I am building a sort of API with ExpressJS that responds to web requests. As of right now, there are three routes in the program, '/login', '/register', and '/changePassword'. All of these methods do not need any sort of token to be processed.
However, every other route I plan to add to the program, (for example, a '/post' route) would require that the user authenticate themselves with a token obtained from a POST request to '/login' with the correct credentials.
TO verify the Token, I have written a middleware function:
module.exports.validateToken = function (req,res,next) {
const token = req.headers['x-access-token']
console.log(`validateToken() - TOKEN: ${token}`)
if (token) {
//Make sure the token is valid[...]
next()
}else {
return res.status(401).send({
message: 'Missing token',
success: false
})
}
}
My question is, how do I apply this middleware to only the routes that would require authentication?
I've thought of just creating another Router object, and calling it like this:
const tokenValidator = require('./util').validate.validateToken
// Router used for any actions that require user-authentication
const authRouter = new app.Router()
authRouter.use(tokenValidator)
But would this interfere at all with my original, authentication free routes?
// Initiate the routes that don't need auth
const routes = require('./routes')(app)
Thanks in advance, I am more of a Java developer, so a lot of the Javascript quirks have left me stumped.
Let's say your middleware is in "./middleware/auth"
I would create a base route for which the middleware should be applied, e.g.
app.use("/private", require("./middleware/auth"));
This will invoke your auth middleware, on any route which starts with '/private'
Thus, any API controller which requires auth should then be defined as:
app.use("/private/foo", require("./controllers/foo"));
Your middlware function will be invoked for any route within /private, before it hits your controller.
And any that do not require your middleware, should simply stay outside of the 'private' api context, e.g.
app.use("/", require("./controllers/somecontroller"));
In Expressjs, every middleware you add, gets added to the middleware stack, i.e. FIFO.
Thus, if you have certain routes, which you'd like to have no authentication, you can simply keep their middlewares above others.
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use(<<pattern>>, authenticate)
Additionally, you can try using nodejs basic-auth module for authentication
Hope this helps!

Conditionally load router middleware Express JS

I have a Express JS code where I load a middleware that defines certain end-points on a router. The endpoints are specific to the user login and logout. I am adding a new authentication in which case I receive my auth token from a different service. When I receive the token from a different service I don't want those end-points to be loaded.
This is my server.js file
let app = express();
const authEndpoints = require('auth'); // this defines router endpoints
const alreadyAuth = require('checkHeaders'); // this middleware checks if request
// already has the auth headers and set res.locals.alreadyAuthenticated to true else false
app.use('/', alreadyAuth);
app.use('/',function(req, res, next) {
if(res.locals.alreadyAuthenticated === false)
next();
else {
console.log('authentication already exists skipping authEndpoints loading');
next('route');
}
}, authEndpoints); // login, logout
//continue here
app.use('/',nextMiddleware);
auth.js file
'use strict';
const express = require('express');
const path = require('path');
const router = express.Router();
router.get('/login', (req, res) => {
// some code
res.sendFile('login.html');
}
router.get('/logout', (req, res) => {
// some code
});
module.exports = router;
I see the console log that prints 'authentication already exists skipping authEndpoints loading' but the endpoints /login and /logout are still accessible.
Also when I comment the whole section
app.use('/',function(req, res, next) {
if(res.locals.alreadyAuthenticated === false)
next();
else {
console.log('authentication already exists skipping authEndpoints loading');
next('route');
}
}, authEndpoints); // login, logout
then the endpoints are not loaded.
Can someone please clarify If this not the way next('route') should be used.
From the top of my head, try adding a isAuthenticated (whatever is your equivalent to this) check to the /login and /logout routes code you listed. if it's authenticated, do a redirect to the protected page, else return the user the login form (or logout thing..). :)
I think that is better if you use the middleware "alreadyAuth" for every request, no matter the route:
app.use(alreadyAuth);
In this way you check the headers in every request for every route. In the "checkHeaders" middleware, you must use an if statement that redirect the user to the login page if is not authenticated, and use next() in the case that is already authenticated.
let checkHeaders = function(req, res, next) {
//some code that check headers
if(isAuthenticate === false) {
res.redirect("/login");
} else {
next();
};
}
Now, all the end points after this middleware are not accessible if the user is not authenticated. So you can use a logout endpoint, or whatever.
Good Luck!

NodeJS & Express authentication middleware not functioning correctly

I am attempting to run the function isUserAuthenticated on every request to the server by requiring it in app.js and 'using' it as so: app.use(authenticate.isUserAuthenticated).
I have an /authenticate callback route that is being POSTED to by our SAML Identity Provider which contains the information required to validate the user and the session. This is what is inside my authenticate.js file:
module.exports = router;
module.exports.isUserAuthenticated = function(req, res, next) {
console.log(req.cookies.subject);
if (req.cookies.subject) {
console.log(req.cookies.subject)
return next();
} res.redirect("LINK TO IDP FOR VERIFICATION, callback func. is then ran to get value of user and session");
}
As referenced, this authentication function is being required and used in app.js: authenticate = require('./routes/authenticate'), and app.use(authenticate.isUserAuthenticated).
The problem: No matter what variation of the if statement to verify if the subject cookie is present in the request, the authentication check is not being fired and the redirect to the IDP authentication route is not being redirected too. The console.log checks in the code above are returning:
undefined, and
{}.
Authentication was working on a single route when I was using the isUserAuthenticated function manually like this: router.use('/', isUserAuthenticated, function(req, res, next) {..., but I am trying to use this function globally so I don't have to manually incorporate this middleware on each route.
Any advice/suggestions would be greatly appreciated. Thank you.
as suggested in comment,
you can move the isUserAuthenticated function to app.js. It'd look something like this
app.use(function(req, res, next) {
if (req.cookies.subject) {
next();
}
else
res.redirect("LINK TO IDP FOR VERIFICATION, callback func. is then ran to get value of user and session");
})
This will process all the requests before they are forwarded to the routes later.
A middleware needs to be set on router object if you are using express js
router.use(isUserAuthenticated)
Don't forget to put this on the top of your routes file.
See the difference between app level and router level middleware here

Categories

Resources