JWT token not verifying only on first entry - node.js

I have a backend API for handling users with JWT and passsport, using Node.js and Express. The frontend is on Vue and everything works alright, except when a new User signs in, the backend authenticator seems to get a jwt_payload that has an id (which is supposed to be the user's id) that doesn't correspond to anybody. Upon refreshing the page, suddenly the jwt_payload has the correct Id and can find a user. The following is the code for the authenticator and JWT strategy:
JWTStrategy:
module.exports = passport => {
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id)
.then(user => {
if(user) return done(null, user);
return done(null,false);
})
.catch(err =>{
return done(err, false, {message:"Hubo un error en el servidor"});
});
})
);
};
Authenticator:
module.exports = (req,res,next) => {
passport.authenticate("jwt", function(err,user,info){
if(err) return next(err);
if(!user) return res.status(401).json({message:"Acceso denegado - No token"});
req.user = user;
next();
})(req,res,next);
};
I followed a guide on how to set up jwt authentication and authorization, users can sign in an register as normal. If it is useful, the Vue frontend checks for a jwt token in localStorage as a second barrier for authorization to the home page, upon openning the home page (which has data that depends on the current user), I get that 401 from the authenticator. I am using vue router to push the home page when the user logs in (logging in saves the jwt token to the localStorage).

Related

Can Hacker modify the request that is sent to the web server? I am authenticating the user based on a object in the request

I am a beginner to nodejs and I am creating my web app. I use passportJs for authentication. As it is mentioned in the documentation that when the user is successfully authenticated, req.user will be created and can be accessed in any route.
My admin.handlebars
router.get('/' , (req , res)=>{
const current_user = req.user
if (!req.user) {
res.redirect('/login')
} else{
res.render('admin/index',{user_data:current_user })
}
})
Authentication using passportJS
passport.use(new LocalStrategy({usernameField:'email'}, (email,password,done)=>{
userModel.findOne({email:email}).then((user)=>{
if (!user) return done(null,false,{message: "No User found"})
bcrypt.compare(password,user.password,(err,matched)=>{
if(err)return err
if(matched){
return done(null,user)
}else {return done(null,false,{message:"Incorrect Password"})}
})
})
}))
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
userModel.findById(id, function(err, user) {
done(err, user);
});
});
router.post('/login',
(req,res,next)=>{
passport.authenticate('local', { successRedirect: '/admin',
failureRedirect: '/login',
failureFlash: 'Invalid username or password.'}
)(req,res,next)}
)
Here is my question:
As you see user will be redirected to admin page only if the .user exists in the req. So can a hacker add an empty .user to the request and access my admin page?
Its kind of weird question tho. Is there any better way to do this? Thanks in advance :)
End-user(in your case hacker) can add any type of data to any request. So yes, end-user can modify requests to send req.user within it. However, they won't be able to access the data within it and their request will not be accepted on your "admin" endpoint if you use req.isAuthenticated().
This is because passport JS serialises the user and stores the information in session after encryption. So UNLESS the end-user (Hacker) has access to another user's machine and copies all the session details (Browser's don't allow other sites to access another sites session) from their browser and use it, they won't be able to use admin.
TLDR;
No they wont be able to access "admin" endpoint by simply adding req.user in their request.

Attach the current user to the request with bearer strategy in passport azure ad

I have a NodeJS API server using express and passport for authentication. I am using the passport azure ad bearer strategy for authentication with Microsoft Azure AD. In the examples provided in the documentation(https://github.com/Azure-Samples/active-directory-node-webapi/blob/master/node-server/app.js#L50), the owner (currentUser) is a javascript variable defined globally. How do I attach the user to the request so that I can do something like
server.post('/user', passport.authenticate('oauth-bearer', {
session: false
}), function(req, res, next){
console.log(`I am the current user ${req.user}`);
});
From the example mentioned in the OIDC strategy, I have seen a deserializeUser and serializeUser function that would allow the user ID to be stored in the session, However, in the bearer strategy, we do not have any sessions maintained as authentication will be performed for every incoming request.
This may be a gap in my understanding. I have gone through the documentation for the individual libraries. Please help me if there is a way this can be done
Following is the sample code via which you can embedd the yser object to request object . Thus when you will do ${req.user} in post callback you will get the user object
const BearerStrategy = require('passport-azure-ad').BearerStrategy;
var bearerStrategy = new BearerStrategy(options,
function(token, done) {
findById(token.oid, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
// "Auto-registration"
return done(null, token);
}
const owner = token.oid;
return done(null, user, token);
});
}
);

How to add JWT authentication with passport-steam

Simple JWT authenticated mern app (here) in place using redux and create-react-app. I want to add passport-steam as an authentication method, however I'm not sure how to handle adding a JWT to the passport-steam strategy. Below is what I have:
// Steam
router.get('/steam', passport.authenticate('steam', { session: false }));
router.get(
'/steam/return',
passport.authenticate('steam', { session: false }),
(req, res) => {
const user = req.user
jwt.sign(
{ id: user.id },
'JWT_secret',
{ expiresIn: '2h' },
(err, token) => {
if (err) throw err;
res.json({
user: user,
token,
});
}
);
res.redirect('/')
}
)
Using the default steam strategy It works and user data is added to the DB, but there's no token in local storage. I'm not sure how to do it with steam as I'm not dispatching an action/not sure If I can. Do I need to authenticate via steam, grab and save the data to the database and then dispatch another action to retrieve it and add the JWT, or is there a better method?

Facebook-passport with JWT

I've been using Passport on my server for user authentication.
When a user is signing in locally (using a username and password), the server sends them a JWT which is stored in localstorage, and is sent back to server for every api call that requires user authentication.
Now I want to support Facebook and Google login as well. Since I began with Passport I thought it would be best to continue with Passport strategies, using passport-facebook and passport-google-oauth.
I'll refer to Facebook, but both strategies behave the same. They both require redirection to a server route ('/auth/facebook' and '/auth/facebook/callback' for that matter).
The process is successful to the point of saving users including their facebook\google ids and tokens on the DB.
When the user is created on the server, a JWT is created (without any reliance on the token received from facebook\google).
... // Passport facebook startegy
var newUser = new User();
newUser.facebook = {};
newUser.facebook.id = profile.id;
newUser.facebook.token = token; // token received from facebook
newUser.facebook.name = profile.displayName;
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
newUser.jwtoken = newUser.generateJwt(); // JWT CREATION!
return done(null, newUser);
});
The problem is that after its creation, I don't find a proper way to send the JWT to the client, since I should also redirect to my app.
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
successRedirect : '/',
failureRedirect : '/'
}), (req, res) => {
var token = req.user.jwtoken;
res.json({token: token});
});
The code above redirects me to my app main page, but I don't get the token.
If I remove the successRedirect, I do get the token, but I'm not redirected to my app.
Any solution for that? Is my approach wrong? Any suggestions will do.
The best solution I found for that problem would be to redirect to the expected page with a cookie which holds the JWT.
Using res.json would only send a json response and would not redirect. That's why the other suggested answer here would not solve the problem I encountered.
So my solution would be:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
successRedirect : '/',
failureRedirect : '/'
}), (req, res) => {
var token = req.user.jwtoken;
res.cookie('auth', token); // Choose whatever name you'd like for that cookie,
res.redirect('http://localhost:3000'); // OR whatever page you want to redirect to with that cookie
});
After redirection, you can read the cookie safely and use that JWT as expected. (you can actually read the cookie on every page load, to check if a user is logged in)
As I mentioned before, it is possible to redirect with the JWT as a query param, but it's very unsafe.
Using a cookie is safer, and there are still security solutions you can use to make it even safer, unlike a query param which is plainly unsecure.
Adding to Bar's answer.
I prepared a landing component to extract the cookie, save it to local storage, delete the cookie, then redirect to an authorized page.
class SocialAuthRedirect extends Component {
componentWillMount() {
this.props.dispatch(
fbAuthUser(getCookie("auth"), () => {
document.cookie =
"auth=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
this.props.history.push("/profile");
})
);
}
render() {
return <div />;
}
}
A proper solution would be to implement the redirection on the client side.
Simply use:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false,
failureRedirect: '/login'
}), (req, res) => {
res.json({
token: req.user.jwtoken
})
}
)
If you're client side receives the token, then redirect from there to home page, and in the case the login wasn't successful, it would be redirected by the server directly.
Or you can go for the full client side management as I would:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
session: false
}), (req, res) => {
if (req.user.jwtoken) {
res.json({
success: true,
token: req.user.jwtoken
})
} else {
res.json({
success: false
})
}
}
)
If success === true, store JWT in LocalStorage, else redirect to login page.

Passport & JWT & Google Strategy - Disable session & res.send() after google callback

Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}

Resources