How to pass jwtPayload data decoded by Passport to nodejs API - node.js

I am using this passport strategy.
passport.use(
'onlyForRefreshToken',
new JWTStrategy(
{
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: jwtSecretRider,
},
(jwtPayload, done) => {
if (jwtPayload) {
return done(null, jwtPayload);
}
return done(null, false);
},
),
);
My goal is Putting 'jwtPayload' into my rest API of Nodejs that is located at other folder.
That is, I want to use jwtPayload decoded at the code below.
exports.riderRefreshToken = async (req, res) => {
const { email } = req.body;
const exRiderRefreshToken = await Rider.findOne({ email });
}
And this router works by middleware of jwtstrategy.
router.post(
'/refreshToken',
passport.authenticate('onlyForRefreshToken', { session: false }),
authenticationCtrl.riderRefreshToken,
);
In conclusion, when JWT passes from jwtstrategy without problem, that Post router would work.
And I want to use jwtPayload that is in jwtstrategy into Nodejs API as req.params or req.body.
Could you help me this problem?

You need to wrap your strategy into a function that gets req and res :
const isAuthenticated: RequestHandler = (req, res, next) => {
passport.authenticate(
'jwt',
{ session: false, failWithError: true },
(error, user) => {
if (error) {
return next(error)
}
//HERE PUT USER WHERE YOU WANT
//req.data or req.user or req.userInfo
req.user = user
return next()
}
)(req, res, next)
}
I don't recommend putting the user into req.params nor req.body since it might be confusing later on (because technically it doesn't come from those).

Related

Passport js with Graphql Shield

How can we use graphql shield with passport js for JWT authentication
I have used the bellow code to implement that functionality but i think the parameters are not allowing the function to run
// graphql_shield.ts
const isAuthorized = rule({ cache: false })(
async (parent, args, ctx, info):Promise<any> => {
return await passportAuth.authenticateJwt;
},
);
// passport.ts
async authenticateJwt (req:any, res:Response, next:NextFunction) {
passport.authenticate('jwt', { session: false }, (error, user) => {
if (user) {
req.logged_in_user = user;
}
next();
})(req, res, next);
}
I want to use middleware to authenticate the JWT in the Bearer token

passport-azure-ad always redirect to failureRedirect but no error shown (nodejs)

following this tutorial I have build my azure ad login to use the graph api
https://learn.microsoft.com/en-us/graph/tutorials/node?tutorial-step=3
The response from azure looks got to me and I can't find any error message but it always triggers the failure redirect and therefore it does not proceed with the signInComplete function.
I was not able to figure out what's wrong. Any hint where I could look to at least to get an idea what triggered the failureRedirect?
// Callback function called once the sign-in is complete
// and an access token has been obtained
async function signInComplete(iss, sub, profile, accessToken, refreshToken, params, done) {
logger.debug("signInComplete function called")
if (!profile.oid) {
logger.error("No OID found in user profile.")
return done(new Error("No OID found in user profile."));
}
return done(null);
}
// Configure OIDC strategy
passport.use(new OIDCStrategy(
{
identityMetadata: `${config.OAUTH_AUTHORITY}${config.OAUTH_ID_METADATA}`,
clientID: config.OAUTH_APP_ID,
responseType: 'code id_token',
responseMode: 'form_post',
redirectUrl: config.OAUTH_REDIRECT_URI,
allowHttpForRedirectUrl: true,
clientSecret: config.OAUTH_APP_PASSWORD,
validateIssuer: false,
passReqToCallback: false,
scope: config.OAUTH_SCOPES.split(' '),
loggingLevel: 'warn'
},
signInComplete
));
office365Signin: function (req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res,
prompt: 'login',
failureRedirect: '/',
failureFlash: true,
successRedirect: '/'
}
)(req,res,next);
},
office365SigninCallback: function(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res,
failureRedirect: process.env.FRONTEND_URL+'fail',
failureFlash: true,
successRedirect: process.env.FRONTEND_URL
}
)(req,res, next);
//failureRedirect gets always trigger even if the req.body looks correct and no error message is send
//Therefore we just check the body and trigger next if the body looks correct
var util = require('util');
logger.debug("office365SigninCallback res: "+util.inspect(res.req.body));
if (res.req.body.code && res.req.body.id_token) {
logger.debug("req.body seems correct there");
}
},
Logger office365SigninCallback res.req.body output:
office365SigninCallback res: { code: 'OAQABAAIAAAAm-06blBE1TpVMil8KPQ41qTM1deUhK_bLgEaGpsiIg5_3sa0ZNEBusd3m4rpBCrXflEsSvEtyjWWzqDhQ_9MybvYdqiR5B2FB59Msd7g8uL5YFcAExrGDqLzYo8xVIaZexHej_K3gDdJFfXbZZsiL6umdepdEXa1pyPIv4S8xVRHPcTyoB80RxpPp97uBCZagR7WstIF0QkfauUxklwlmOygAWjFvIMTuSijkkVZZ-04MbSX6wT3vBwJmQ2-kj6x_W_9fdCbYtdavgR6ZlYKtdiAxVm-3qULweEfvFo8RVC5xV2wdaPKqqYh41lcAq_1NHCiTdUcmmxbk8177WGzabDbH-rM-jRzORamSbLg_0vF48KWqu9zSgiCTX4RW556akFo6pcSkpriJWZH1aVl1cSMTWM64zb9tRM08O7hJ9YyFGTM-n6RpIiA3h9-Xh1E_TEZ8sG0noVId3yN8-gJXZ-pEB7Bur8s5C3DFOOlPgqgdEDj16tM8Wg0RinaL8P1BJ18k_Y_pr-huHMzhKaVCLYCX1Urq8fDomv0UAVchDQNIjdQ5PfiiYIT-0GYYzR5BB_5wGKJgwZypae89RRXpNJw-XOY5dv10jsUk3jEHRXW5xle2HtpM5DgCs6VbuxwVuaJfrRhfNdy7WkoOT3caV-4qTYfpfqwKvX_YtdD15RMpg-BVZQyI8b12meomlHdRi2aiqwqpTfJas0mrE7jHeScQErWx0qWAhvnZS8JJbauJGbXvjCbl2Tcoh19ngaggAA',
id_token:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkN0VHVoTUptRDVNN0RMZHpEMnYyeDNRS1NSWSJ9.eyJhdWQiOiI1YTYxYTMwZC00MTAyLTQwMGUtYTVmNS05NDJlYzAxNDE3ODciLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vZTFkMDE4YTktOWRlZC00Yjg4LTllYTItNDJkYTAwY2Q1ZjVhL3YyLjAiLCJpYrRVl4UHJjRk9aVk5tX1N6UHNyX0ZrIiwib2lkIjoiZXQiOjE1ODk4ODA3MTIsIm5iZiI6MTU4OTg4MDcxMiwiZXhwIjoxNTg5ODg0NjEyLCJhaW8iOiJBVFFBeS84UEFBQUEzb0dsSkdWK1dYYlE0elZJNHZic2ZnejZvUWZqYy9XUkNtS1QrYlRORHVjM2dYdll4L3F4OTdEMm5VeTRUUUxNIiwiY19oYXNoIjoiU1k4alM1ZjlxMkg4TU9oSmVIcjNhQSIsIm5hbWUiOiJUaW0gU3RlaW4iLCJub25jZSI6IkZfWjctTmpGSU5jgyYWRkNGMtOGM2MS00YjYxLWJmYWItZmQzNmE2YzY0MjY5IiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHN0ZWluQFZBTlRBLklPIiwic3ViIjoiZ3Q3RExJZ1Y0MEJDZlRxRXhhSlM5Z09qcjhlRWU3TFF0cTJLQk9yNThmbyIsInRpZCI6ImUxZDAxOGE5LTlkZWQtNGI4OC05ZWEyLTQyZGEwMGNkNWY1YSIsInV0aSI6IjlyRHBOM0hBLTAtU3Y2T25XWmpVQUEiLCJ2ZXIiOiIyLjAifQ.uCG5x4cesT2925Kr_lXloYWxgIsPfsRX2FKd4t8ASDeQXg9PdvjTsTvnzzBqFDtW77obSX7bO75a-0XjA9TIh4-kMTgJWm8PlnHCWaHRQgfNlTmjp99oUf0msZx6OhyZ0-xFMMe6DTShFfBhHjF2ds17zw-oynv6PaygSox4s94qvL2e8ULi2wfpm4AYQwxXeUQba9dhoQu8AsCozY-6NyWIGc2alzg7TK5qBpuY16BScGsUkmChGFZ9lF9vD-uM8x0JYg0G6Uvc_aDNIWnt9B7VRH-U9sIFXtL9doaJXvRl2aPQnj6x0rtfgfJ4zonrJZQEn7e8y7XPIcnU0gMO9g',
state: 'GNy7cIjlBvfga4FhQiapnWnDAn8itXtk',
session_state: 'c88bace3-4039-9922-6f06-dcd6ba1a62ac' }
Based on the documentation the response looks correct to me:
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
I am grabbing this code straight from one of my projects in production. I was facing a similar problem before but it kind of resolved by itself when we were tweaking the done callback for some reason.
Maybe this can serve as an example.
Passport.js
'use strict';
//Dependencies
const passport = require('passport');
const OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
//Custom Modules
const msService = require('./msService');
const DB_Connection = require('../dbConnection');
//DB Setting
require('../models/MS_User');
const User_DB = DB_Connection.model('user');
//Winston Logger
const logger = require('../log');
const passLog = logger.get('passportLog');
//Azure AD Creds
const loginCredentials = require('../creds/oicdCreds');
// Session
//Take in outlook id => keep the session data small
passport.serializeUser((outlookid, done) => {
done(null, outlookid);
});
//Deserialize when needed by querying the DB for full user details
passport.deserializeUser((outlookid, done) => {
User_DB.findOne({ outlookId: outlookid })
.then(user => {
done(null, user);
})
.catch(err => passLog.error(`Error Deserializing User: ${outlookid}:` + ' ' + err));
});
//Export the passport module
module.exports = (passport) => {
//OpenIdConnect
passport.use(new OIDCStrategy(loginCredentials,
//Verify callback for passReqToCallback: false
(iss, sub, profile, access_token, refresh_token, params, done) => {
//Get Calendar ID
msService.getCalId(access_token, calId => {
//Create or update the user
User_DB.findOneAndUpdate({ outlookId: profile.oid }, {
name: profile.displayName,
outlookId: profile.oid,
email: profile._json.email,
lastLogin: profile._json.ipaddr,
accessToken: access_token,
calId: calId
}, { upsert: true, returnNewDocument: false })
.catch(err => passLog.error(`Error Adding / Rnewing User: ${profile.oid}:` + ' ' + err));
//Return Profile ID for Serialization
done(null, profile.oid);
});
}));
};
The routes
//AD OpenIdConenct
//Login
router.get('/auth/outlook/login',
//Using MS Azure OpenId Connect strategy (passport)
passport.authenticate('azuread-openidconnect')
);
//Callback Handling
//Using MS Azure OpenId Connect strategy (passport)
router.post('/auth/outlook/callback', passport.authenticate('azuread-openidconnect', { failureRedirect: '/auth/outlook/login' }), (req, res) => {
//Redis
client.keys('*', (err, keys) => {
sessionLog.info(`Login Active Session: ${keys.length}`);
});
res.redirect('/profile_info');
}
);
If you come by this in question in google I had the same error, and it turned out I was using the Azure Secret ID, not the Secret Value
Add loggingNoPII: false to your OIDC strategy, this will print out any errors.

Express + PassportJS can't read flash messages

I have my Express + Passport + Firebase project where I handle authentication with a local stratetegy. Since I found that Passport would take care of the authentication process, so I also found that it would accept flash messages as third parameter for the done() function (in the strategy). But I am not sure how to read them:
I guess the flow I made to set and read flash messages were:
Install connect-flash with NPM.
Set the Express middleware after importing it:
import * as flash from 'connect-flash';
...
const app = express();
...
app.use(flash());
Configure Passport Authentication in the Express route according to the documentation:
// POST - /api/v1/admin/oauth/login
router.post(
'/login',
async (req: Request, res: Response) => { /* middleware function to validate input */ },
passport.authenticate('local', {
failureRedirect: '/api/v1/admin/oauth/login',
failureFlash: true
}),
async (req: Request, res: Response) => { /* function after success login */
);
Include the flash messages in the done() method, according to Passport configuration documentation:
import { Strategy as LocalStrategy } from 'passport-local';
import db from '../../config/database';
import * as bcrypt from 'bcryptjs';
export default new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
const ref = db.collection('users').doc(email);
try {
const doc = await ref.get();
if (!doc.exists) {
return done(null, false, { error: 'Wrong email' });
}
const user = doc.data();
const match: boolean = await bcrypt.compare(password, user.password);
if (!match) {
return done(null, false, { error: 'Wrong password' });
}
user.id = doc.id;
delete user.password;
return done(null, user);
} catch(error) {
return done(error);
}
});
Read the flash messages using req.flash('error'):
// GET - /api/v1/admin/oauth/login
router.get('/login', (req: any, res: Response) => {
const result: IResult = {
message: '',
data: null,
ok: false
};
if (req.flash('error')) {
resultado.message = req.flash('error');
console.log(req.flash('error'));
}
return res.status(400).json(result);
});
I thought it was theroically working in my mind, until step 5, where req.flash('error') has an empty array in it. What I am doing wrong?
You're passing the flash message wrong!
The 3rd argument of done() should be an object with the fields type and message:
return done(null, false, { message: 'Wrong email' });
The type defaults to error.
This API doesn't seem to be documented explicitly, but is shown in the 3rd example of the Verify Callback section in the Configure chapter of the Passport.js documentation.
I've created a repo with a minimally reproducible working example.
I keep searching and I found a solution but it works in the second login attempt.
Steps from my question I modified to make it work:
Install connect-flash with NPM.
Set the Express middleware after importing it:
import * as flash from 'connect-flash';
...
const app = express();
...
app.use(flash());
Configure Passport Authentication in the Express route according to the documentation:
// POST - /api/v1/admin/oauth/login
router.post(
'/login',
async (req: Request, res: Response) => { /* middleware function to validate input */ },
passport.authenticate('local', {
failureFlash: true,
failureRedirect: '/api/v1/admin/oauth/login'
}),
async (req: Request, res: Response) => { /* function after success login */
);
Create another route so it can display the flash message, thanks to #Codebling:
// GET - /api/v1/admin/oauth/login
router.get('/login', (req: Request, res: Response) => {
const result: IResult = {
message: 'Auth ok',
data: null,
ok: true
};
let status: number = 200;
const flashMessage: any = req.flash('error');
if (flashMessage.length) {
resultado.message = flashMessage[0];
resultado.ok = false;
status = 400;
}
return res.status(status).json(result);
});
Include the flash messages in the done() method, according to Passport configuration documentation:
import { Strategy as LocalStrategy } from 'passport-local';
import db from '../../config/database';
import * as bcrypt from 'bcryptjs';
export default new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
const ref = db.collection('users').doc(email);
try {
const doc = await ref.get();
if (!doc.exists) {
return done(null, false, { message: 'Wrong email' });
}
const user = doc.data();
const match: boolean = await bcrypt.compare(password, user.password);
if (!match) {
return done(null, false, { message: 'Wrong password' });
}
user.id = doc.id;
delete user.password;
return done(null, user);
} catch(error) {
return done(error);
}
});

How to bind or pass req parameter to Passport.js JWT Strategy?

I want to store information in the database when user is authenticated. The information is coming form the client in the request. The following code throws error, saying req is not defined.
Controller:
exports.verifySession = async function(req, res, next) {
let responses = [];
passport.authenticate('jwt', async (error, result) => {
if (error) {
email.sendError(res, error);
} else if (result === false) {
responses.push(new CustomResponse(1).get());
return res.status(422).json({ data: { errors: responses } });
}
if (result.SessionToken) {
return res.status(200).json('valid');
} else {
return res.status(401).json();
}
})(req, res, next);
};
And passport.js:
passport.use(
new JWTstrategy(
{
// We expect the user to send the token as a query paramater with the name 'token'
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
// Secret we used to sign our JWT
secretOrKey: config.jwtkey
},
async (token, done) => {
console.log(req.body);
try {
const user = new User();
user.UserID = token.user.UserID;
user.SessionToken = token.user.SessionToken;
user.SessionDate = token.user.SessionDate;
user.ProviderID = token.user.ProviderID;
// Verify session token
await user.verifySessionToken(user, async (error, result) => {
if (error) {
return done(error);
} else if (result.returnValue === 0) {
return done(null, token.user);
} else if (result.returnValue !== 0) {
return done(null, result);
}
});
} catch (error) {
done(error);
}
}
)
);
You can use passReqToCallback feature of passport to pass your request body to passport.
From passport.js official docs :
The JWT authentication strategy is constructed as follows:
new JwtStrategy(options, verify)
options is an object literal
containing options to control how the token is extracted from the
request or verified.
...
...
passReqToCallback: If true the request will be passed to the verify
callback. i.e. verify(request, jwt_payload, done_callback).
You can try this:
passport.use(new JWTstrategy({
// We expect the user to send the token as a query paramater with the name 'token'
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
// Secret we used to sign our JWT
secretOrKey: config.jwtkey,
//this will help you to pass request body to passport
passReqToCallback: true
}, async (req, token,done) => {
//req becomes the first parameter
// now you can access req.body here
})
Note: req becomes the first parameter of callback function instead of token, when you use passReqToCallback

Async login with mongodb, node.js and passport

So, I got everything to work, up until the routing doesn't seem to be getting the data I'm sending into the user. But, if console.log inside of passport, it spits out the correct information. So here is my passport code, which works for the most part:
const LocalStrategy = require('passport-local').Strategy;
const db = require('mongodb');
const bcrypt = require('bcryptjs');
const config = require('./config');
module.exports = async (passport) => {
// =========================================================================
// passport session setup ==================================================
// =========================================================================
// used to serialize the user for the session
passport.serializeUser((user, done) => {
done(null, user._id);
});
// used to deserialize the user
passport.deserializeUser(async(id, done) => {
let userData = await userDb().findOne({ '_id': id});
done(null, userData);
});
// Local Strategy login
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passReqToCallback: true,
}, async (req, username, password, done) => {
console.log('Pulled up: ' + username);
let userDb = await usersDb();
let userData = await userDb.findOne({ 'email': username})
// Check if user exists
if (userData === null) {
console.log('User doesn\'t exist');
return Promise.reject('Email or password incorrect.');
} else {
// if user exists check password
let passCheck = await bcrypt.compareSync(password, userData.password);
if (passCheck) {
console.log('Password Correct');
return done(null, userData);
} else {
// if password is wrong
console.log('Password incorrect');
return Promise.reject('Email or password incorrect.');
}
}
}));
// DB collection
async function usersDb() {
const client = await db.MongoClient.connect(
config.database,
{
useNewUrlParser: true
}
);
return client.db('kog').collection('users');
}
};
heres the login route:
router.post('/login',
passport.authenticate('local-login', {
successRedirect: '/game',
failureRedirect: '/',
}), (req, res) => {
});
But my issues lies here:
// Get game route
app.get('/game', async (req, res) => {
if (req.user) {
res.render('game');
} else {
console.log('Forced redirect');
res.redirect('/');
}
});
Thought of another block that may be of an issue:
app.get('*', async (req, res, next) => {
res.locals.user = await req.user || null;
next();
});
No matter what I do I seem to not be able to get the routing check to pull up the user data. I am not sure where I am going wrong here, as it works all up to that point. I will successfully "login" but will result in be being forcefully redirected to '/' even if everything as worked correctly.
I am fairly certain it is the fact I probably am not handling the async/await stuff correctly, but I'm not sure where I am having problems.
I think you are missing :
passport.authenticate('local')
Which triggers your passport.js file localStrategy. Not sure how you got the passport file running to check to do the console.
For more, refer: http://www.passportjs.org/docs/authenticate/

Resources