How to send/extract JWT token in nodejs with passport-jwt? - node.js

I've tried to check if they're online examples of how to use JWT extractors to get the token from the request but I failed to understand how to send the token with the request after the user logins.
When I use Postman, there's a tab called Authorization where I can choose the type Bearer Token which enabled me to add the token with the Authorization and the request http://localhost:5000/profile went successfully.
However, the browser stills showing me only Unauthorized when I try to access the profile http://localhost:5000/profile after successful login.
POSTMAN SCREEN-SHOT:
BROWSER SCREEN-SHOT:
I've followed the passpot-jwt documentation configuration:
passport.use(
new JWTStrategy(
{
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: "mysecret",
},
function (jwtPayload, done) {
return User.findOne({ username: jwtPayload.username })
.then((user) => {
return done(null, user);
})
.catch((err) => {
return done(err);
});
}
)
);
And my login route looks like :
Router.post("/", (req, res, next) => {
passport.authenticate("local", { session: false }, (err, user, info) => {
if (err) return next(err);
if (!user) {
return res.redirect("/login?info=" + info);
}
req.logIn(user, { session: false }, (err) => {
if (err) return next(err);
const token = jwt.sign({ username: user.username }, "mysecret");
res.json({ user, token: `Bearer ${token}` });
});
})(req, res, next);
});

The issue is:
I was trying to access the profile without adding the Authorization in the header from the server itself. The Authorization contains the generated token.
With Postman I was able to do that with the UI as explained above. However, in the code, I needed to create a middleware before accessing the profile route.
app.use(
"/profile",
(req, res, next) => {
req.headers.authorization = `Bearer ` + req.cookies["authentication-token"];
next();
},
profileRouter
);

After login, you can send Authorization Token in headers
(function () {
fetch("http://localhost:5000/profile", {
method: "GET",
headers: {
"Content-Type": "text/plain",
"X-My-Custom-Header": "value-v",
Authorization:
"Bearer " + Token,
},
});
})();
Hope you got some idea.

Related

Google People API: Request had invalid authentication credentials - Authorization Bearer header not setting up

I am trying to implement this Google People api for a chat application that I am working on. people API docs has only this example - https://github.com/googleworkspace/node-samples/blob/master/people/quickstart/index.js
I made some changes to use integrate it with my project.
// imports
const app = express();
app.use(cookieParser());
const SCOPES = ['https://www.googleapis.com/auth/contacts.readonly'];
const people = google.people('v1');
let credentials, oAuth2Client;
fs.readFile('./client_secret.json', async (err, content) => {
if (err) return console.error(err);
credentials = JSON.parse(content);
const { client_secret, client_id, redirect_uris } = credentials.web;
oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[1]);
});
app.get("/auth/google", (req, res) => {
console.log(req.cookies);
res.cookie("sample_cookie", "sample_value");
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
res.redirect(authUrl);
});
app.get("/contacts", (req, resp) => {
if (req.cookies.access_token && req.cookies.refresh_token) {
const token = {
access_token: req.cookies.access_token,
refresh_token: req.cookies.refresh_token,
scope: req.cookies.scope,
token_type: "Bearer",
expiry_date: req.cookies.expiry_date,
}
oAuth2Client.setCredentials(token);
const service = google.people({ version: 'v1', oAuth2Client });
service.people.connections.list({
resourceName: 'people/me',
pageSize: 10,
personFields: 'names,emailAddresses,phoneNumbers',
}, (err, res) => {
if (err) return resp.send(err);
const connections = res.data.connections;
if (connections) {
connections.forEach((person) => {
if (person.names && person.names.length > 0) {
resp.write(person.names);
} else {
resp.write('No display name found for connection.');
}
});
} else {
resp.write('No connections found.');
}
resp.end();
});
} else {
res.send("Something's wrong yet.")
}
})
app.get(["/auth/google/callback", "authorized"], async (req, res) => {
const code = req.query.code;
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
res.cookie("access_token", token.access_token);
res.cookie("refresh_token", token.refresh_token);
res.cookie("scope", token.scope);
res.cookie("token_type", token.token_type);
res.cookie("expiry_date", token.expiry_date);
res.send("Done.")
})
})
app.listen(3000, () => {
console.log("running");
})
but I am getting 401: unauthorized. All the changes that I made to the former (Google) example is just that instead of saving details to token, I am saving them as cookies, and I added routes to access it from browser. The example provided by Google works as expected. The changes I made works as well till the authorization point but when trying to access contacts route it returns the following response.
this is the response I am geeting (only included details that I believe to be necessary):
{
"response": {
"config": {
"oAuth2Client": {
"credentials": {
"access_token": "my_access_token",
"refresh_token": "my_refresh_token",
"scope": "https://www.googleapis.com/auth/contacts.readonly",
"token_type": "Bearer",
"expiry_date": 1609256514576
},
"redirectUri": "http://localhost:3000/auth/google/callback",
},
"url": "https://people.googleapis.com/v1/people/me/connections?pageSize=10&personFields=names%2CemailAddresses%2CphoneNumbers",
"method": "GET",
"headers": {
"Accept-Encoding": "gzip",
"User-Agent": "google-api-nodejs-client/0.7.2 (gzip)",
"Accept": "application/json"
},
},
"data": {
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [{
"message": "Login Required.",
"domain": "global",
"reason": "required",
"location": "Authorization",
"locationType": "header"
}],
"status": "UNAUTHENTICATED"
}
},...
I tried to debug the code. I can't catch anything. But one thing I noticed is that in the above response I do not have Authorization header set. In successful API request from the google docs example, I receive
{
"config": {
"url": "https://people.googleapis.com/v1/people/me/connections?pageSize=10&personFields=names%2CemailAddresses%2CphoneNumbers",
"method": "GET",
"headers": {
"Accept-Encoding": "gzip",
"User-Agent": "google-api-nodejs-client/0.7.2 (gzip)",
"Authorization": "Bearer access-code",
"Accept": "application/json"
},
},
"data": {
"connections": [{...
I Don't get why my code isn't setting the Authorization header, also OAuthClient and credentials field is not present in this successful response. If instead of people api I try something like below in contacts route or make a GET request with Bearer token in postman I get the response correctly.
let bearer = `Bearer ${req.cookies.access_token}`;
request({
url: 'https://people.googleapis.com/v1/people/me/connections?pageSize=10&personFields=names%2CemailAddresses%2CphoneNumbers',
headers: {
'Authorization': bearer
}}, (err, res) => {
if (err) {
console.error(err);
} else {
resp.send(res);
}
}
);
I recieve the response correctly. But I do not want to do it this way. I can't figure out what's wrong with my code or if someone can provide any other working example... I also tried using passport.js and I get the same 401 unauthorized error.
// passport-setup.js
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use(new GoogleStrategy({
clientID: "client-id",
clientSecret: "client-secret",
callbackURL: "http://localhost:3000/auth/google/callback",
passReqToCallback: true
},
function (req, accessToken, refreshToken, otherTokenDetails, profile, done) {
req.session.accessToken = accessToken;
req.session.refreshToken = refreshToken;
req.session.scope = otherTokenDetails.scope;
req.session.token_type = otherTokenDetails.token_type;
req.session.expiry_date = new Date().getTime() + otherTokenDetails.expires_in;
return done(null, profile);
}
));
index.js
// importing express, cors, bodyParser, passport, cookieSession, passport setup and googleapis
const app = express();
const people = google.people('v1');
// app.use(cors, bodyParser, cookieSession, passport init and session)
const isLoggedIn = (req, res, next) => {
if (req.user) {
next();
}
else {
res.sendStatus(401);
}
}
app.get("/success", isLoggedIn, (req, resp) => {
const oAuth2Client = new google.auth.OAuth2(id, secret and url...)
const token = {
access_token: req.session.accessToken,
refresh_token: req.session.refreshToken,
scope: req.session.scope,
token_type: req.session.token_type,
expiry_date: req.session.expiry_date,
}
oAuth2Client.setCredentials(token);
const service = google.people({ version: 'v1', oAuth2Client });
service.people.connections.list({
resourceName: 'people/me',
pageSize: 10,
personFields: 'names,emailAddresses,phoneNumbers',
}, (err, res) => {
if (err) return resp.send(err);
const connections = res.data.connections;
if (connections) {
console.log('Connections:');
connections.forEach((person) => {
if (person.names && person.names.length > 0) {
resp.write(person.names);
} else {
resp.write('No display name found for connection.');
}
});
} else {
resp.write("No connections.");
}
res.end();
});
})
app.get('/auth/google',
passport.authenticate('google', {
scope: ['profile', 'email', 'https://www.googleapis.com/auth/contacts'],
accessType: 'offline',
prompt: 'consent',
}));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function (req, res) {
res.redirect('/success');
});
app.listen(3000, () => console.log("Server is up and running on port 3000."));
I have checked almost every similar Stack Overflow answer and GitHub issues. Nothing seems to work out.
The name for the second parameter that is passed into google.people is auth.
In JavaScript you can write {auth: auth} as simply {auth}. In the example provided by google, the name of the variable is same as the field name, That's why it is directly supplied as auth
const service = google.people({ version: 'v1', auth });
But your variable name is different than the field name. so change the name or just replace this one line with
const service = google.people({ version: 'v1', auth: oAuth2Client });
It is expecting auth as the second property but it receives a property wth name
oAuth2Client that's why it is not working.

Refreshing JWT access token with refresh token within single middleware function on a post route

I'm trying to learn JWT authentication in express and one thing that I'm came across this code from Github
that this guy has initialised an middleware function to authenticate and check expiry of access token as per below:
app.post("/protected", auth, (req, res) => {
return res.json({ message: "Protected content!" });
})
async function auth(req, res, next) {
let token = req.headers["authorization"];
token = token.split(" ")[1]; //Access token
jwt.verify(token, "access", async (err, user) => {
if (user) {
req.user = user;
next();
} else if (err.message === "jwt expired") {
return res.json({
success: false,
message: "Access token expired"
});
} else {
console.log(err);
return res
.status(403)
.json({ err, message: "User not authenticated" });
}
});
}
and a separate route for refreshing the access token with the help of refresh token
app.post("/refresh", (req, res, next) => {
const refreshToken = req.body.token;
if (!refreshToken || !refreshTokens.includes(refreshToken)) {
return res.json({ message: "Refresh token not found, login again" });
}
// If the refresh token is valid, create a new accessToken and return it.
jwt.verify(refreshToken, "refresh", (err, user) => {
if (!err) {
const accessToken = jwt.sign({ username: user.name }, "access", {
expiresIn: "20s"
});
return res.json({ success: true, accessToken });
} else {
return res.json({
success: false,
message: "Invalid refresh token"
});
}
});
});
So, my question is how secure it is and how can I create single middleware function that could do both authentication and refreshing access token without hitting the app.post('/refresh') as in my view it wouldn't be a smooth experience to deal with it in frontend API management within react
Edit
My middleware seems to work well but it doesn't identify the wrong refresh token and then actually getting worked on protected route
app.post('/home', authenticateUser, (req, res) => {
res.send('welcome');
});
async function authenticateUser(req, res, next) {
let token = req.headers['authorization'];
token = token.split(' ')[1];
jwt.verify(token, JWT_AUTH_TOKEN, async (err, phone) => {
if (phone) {
req.phone = phone;
next();
} else if (err) {
const refreshToken = req.body.refreshToken;
if (!refreshToken || !refreshTokens.includes(refreshToken)) {
return res.json({ message: 'Refresh token not found, login again' });
} else {
jwt.verify(refreshToken, JWT_REFRESH_TOKEN, (err, phone) => {
if (!err) {
const accessToken = jwt.sign({ phone }, JWT_AUTH_TOKEN, { expiresIn: '30s' });
return res.json({ success: true, accessToken });
} else {
return res.json({
success: false,
message: 'Invalid refresh token'
});
}
next();
});
}
} else {
console.log(err);
return res.status(403).json({ err, message: 'User not authenticated' });
}
});
}

Integrating json web token in oauth20

I am trying to create an api where user can sign up with an email or can sign in with google, I use json web token for authentication and oauth20, the problem is, can, I pass a jwt with oauth?
I have tried passing it and, I get a token if, I console log, but how do, I pass it to the user, like can i some way attach it to the req.user object in the cb by oauth or something like that?
I am doing this in the google strategy:
async (accessToken, refreshToken, params, profile, cb) => {
const userCheck = await User.findOne({ googleId: profile.id });
if (userCheck) {
const payload = {
user: {
id: userCheck.id
}
};
jwtToken.sign(
payload,
config.get("jwtSecret"),
{ expiresIn: 360000 },
(err, token) => {
if (err) {
throw err;
}
// console.log(token);
return res.json({ token });
},
cb(null, userCheck)
);
My routes are protected like this:
router.get("/", auth, async (req, res)=>{
...some code
}
where auth is a middle ware function
This is the Auth middleware function:
module.exports = function(req, res, next) {
const token = req.header("x-auth-token");
// If no token found
if (!token)
{
return res.status(401).json({ msg: "User not authorized" });
}
// Set token to user
try {
const decoded = jwtToken.verify(token, config.get("jwtSecret"));
req.user = decoded.user;
}
catch (err)
{
res.
status(401)
.json({ msg: "User not authenticated, please login or sign up" });
}
next();
};
I found the solution, you need to pass sign the token in the passport.serializeUser and then send the it with a redirection in response of the redirect url.
The serialize user function:
passport.serializeUser(async (user, cb) => {
const payload = {
user: {
id: user.id
}
};
token = jwtToken.sign(payload, config.get("jwtSecret"), {
expiresIn: 360000
});
console.log("serialize");
cb(null, user.id);
});
The redirection route:
router.get(
"/google/redirect",
passport.authenticate("google", { sessionStorage: false }),
(req, res) => {
res.redirect("/" + token);
}
);

How to resolve Passport-jwt token unauthorized error?

I am persistently getting 'unauthorized' error while authenticating using a JWT. Below is my controller code:
exports.loginPost = async (req, res) => {
winston.info('Calling loginPost()...');
passport.authenticate('local', { session: false }, (err, user, info) => {
if (err) {
return utils.errorHandler(res, err);
} else if (!user) {
return utils.errorHandler(res, {
statusCode: 403,
message: 'Incorrect username or password.'
});
}
const token = jwt.sign(user, sharedSecret, { expiresIn: '24h' });
//req.user = user;
return res.json({ user, token });
// req.login(user, { session: false }, (err) => {
// if (err) {
// res.send(err);
// }
// // generate a signed json web token with the contents of user object and return it in the response
// const token = jwt.sign(user, sharedSecret, { expiresIn: '24h' });
// //req.user = user;
// return res.json({ user, token });
// });
})(req, res);
};
exports.isUserLoggedIn = async (req, res) => {
let login = {"message": "all good !"}
console.log(req)
return res.status(200).json(login);
//return res.status(200).json(req.user);
};
and passport.js strategy script is as follows:
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async function (username, password, cb) {
//this one is typically a DB call. Assume that the returned user object is pre-formatted and ready for storing in JWT
try {
let user = await userService.getUserWithPassword(username, password);
console.log("passport.js: ",user);
if (!user || user.walletKey !== password) {
throw { statusCode: 403, message: 'Incorrect username or password.' };
}
// // purge password field
// delete user.currentPassword;
return cb(null, user, { message: 'Logged In Successfully' });
} catch (err) {
cb(err);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey: sharedSecret,
passReqToCallback: true
},
async function (req, jwtPayload, cb) {
// Return user object from the JWTPayload
try {
let user = await userService.getUserWithPassword(jwtPayload.walletName, jwtPayload.walletKey);
console.log("passport.js: ",user);
req.user = user
return cb(null, user); //jwtPayload
} catch(err){
return cb(err,false);
}
}
));
I am able to generate token successfully, however, on calling isUserLoggedIn method using Bearer Token, it's prompting me unauthorized error. I am not making an traditional db call to login, instead I am just creating account in a Hyperledger-Indy pool nodes. Using swagger express middleware on a Node.js app.
Adding isUserLoggedIn method script below:
exports.isUserLoggedIn = async (req, res) => {
//let login = {"message": "all good !"}
console.log(req)
return res.status(200).json(req.user);
//return res.status(200).json(req.user);
};

How do I use passport.js to GET JSON using accessToken of an already logged in user?

I'm able to do a GET request to grab JSON from discord while using the access token that's supplied by passport. How can I use passport to grab the accessToken of a logged in user to do GET requests on another page?
passport.use(new DiscordStrategy({
clientID: keys.discord.clientID,
clientSecret: keys.discord.clientSecret,
callbackURL: '/auth/discord/redirect'
}, (accessToken, refreshToken, profile, done) => {
request({
url: 'https://discordapp.com/api/users/#me/guilds',
auth: {
'bearer': accessToken
}
}, (err, res) => {
console.log(res.body);
});
User.findOne({ discordId: profile.id }).then((currentUser) => {
if (currentUser) {
done(null, currentUser);
} else {
new User({
discordId: profile.id
}).save().then((newUser) => {
console.log('Created new user: ', newUser);
done(null, newUser);
});
}
});
}));
So I will skip the passport part and will show you the token exchange:
The signin method:
const jwt = require('jsonwebtoken');
[...]
app.post('/signin', passport.authenticate('signin'), (req, res) => {
if (req.user){
// Set the JWT token for this session
const token = jwt.sign(
{ id: req.user.id },
keys.discord.clientSecret,
{ expiresIn: config.SESSION_DURATION } //The validity time (optional)
);
const currentDate = new Date();
return res.status(200).send({
auth: true,
token: token,
// These two properties below are optional if you want to keep track
// of the session duration and send some more user info
tokenDuration: {
expire: currentDate.setMilliseconds(currentDate.getMilliseconds() + config.SESSION_DURATION)},
user: {firstname: req.user.firstname, lastname: req.user.lastname}
});
}
return res.status(401).send('Could not login');
});
Then when you make requests from the client:
axios({
method: 'POST',
url: `${url}/path`,
data: data,
headers: {
'x-access-token': jwtToken, // This is the "token" property from above
},
json: true
})
And finally you handle the above request in the server:
app.post('/path', (req, res) => {
jwt.verify(req.headers['x-access-token'], keys.discord.clientSecret, (err, decoded) => {
if (err) {
// The user is not authorized, handle properly
}
// User is authorized, do stuff
});
Hopefully this will be enough for you to start. Like I mentioned have a look at JWT, their documentation is well written :)

Resources