Issue with Hapi-jwt: Hapi-jwt authentication not running the handler function - node.js

I am not sure why, but I am having an issue implementing JWT authentication on my API. I'm using the https://www.npmjs.com/package/hapi-jwt package.
Creating the token works without issue, I'm getting a reply back on my /api/v1/login (auth) route, giving me a status:200 and the token:hash.
However, using my basic validation function on any route causes the route's handler to no longer run, and instead the validation function replies with the {"credentials": ... } object.
I'm also using Good and good-console, but I don't believe they are causing any problems in this.
Here's the server code (in the order it appears in my index.js file):
// SERVER SETUP
var server = new hapi.Server();
server.connection({ port: hapiPortNo });
// JWT SERVER REGISTRATIONS
server.register(require('hapi-jwt'), function(err) {
if(err) throw err;
server.auth.strategy('simple', 'bearer-access-token', {
validateFunc: auth.validateJWT,
secret: jwtCodeString
});
});
function defaultHandler(req, reply) {
reply('success!');
}
server.route({
method: 'GET',
path: '/',
handler: defaultHandler,
config: { auth: 'simple' }
});
server.route({
method: 'POST',
path: '/api/v1/login',
handler: auth.authHandler
});
server.register({
register: good,
options: {
reporters: [{
reporter: require('good-console'),
args: [{ log: '*', response: '*' }]
}]
}
}, function (err) {
if(err) {
throw err;
}
// START SERVER
server.start(function () {
server.log('info', 'Server running at: ' + server.info.uri);
});
});
And these are my auth and validation functions (kept in a seperate file, ./lib/auth.js and imported as a requirement):
//Authentication
function authHandler( request, reply ) {
var data = request.payload;
var tokenData = {
"user": data.user
};
var encoded = jwt.sign( tokenData, _codeString);
reply({ "status": 200, "token": encoded });
}
// Validation
function validateJWT( decoded, request, next ) {
var isValid = false;
if(decoded.user == 'me') {
isValid = true;
}
return next(null, isValid, {token: decoded} );
}
The hapi server runs without issues and replies all my routes' data normally when I drop the config: { auth: 'simple' } but for some reason adding authentication is resulting in every route replying with:
{
"credentials": {
"token": {
"user": "me",
"iat": 1425689201
}
}
}
Any thoughts? I'd be open to switching to another JWT auth package if someone has a recommendation.

The issue is with the hapi-jwt plugin, it hasn't been updated to work with hapi 8. Line 81 should be changed from
return reply(null, { credentials: credentials });
to
return reply.continue({ credentials: session });
You can either create a issue in the repository of hapi-jwt and ask the author to update the module, or you can try to use an other module like hapi-auth-jwt2 which is compatible with hapi 8.

Related

Reactjs + Express Twitter API "Could not authenticate you."

I am trying to create a very simple app that allows me to post a tweet. I am currently using React running on port 3000 and express server.js running on port 5000
my server.js has the following:
app.post("/twitter/message", async(req, res) => {
const tweet = req.body.tweet;
try {
const response = await postToTwitter(tweet);
res.json({
message: 'Tweet successfully posted to twitter'
});
} catch (error) {
res.status(500).json({
message: 'Not able to post'
});
}
});
function postToTwitter(tweet) {
client.post(
"statuses/update",
{ status: tweet },
function (error, tweet, response) {
if (error) log(error);
/* log(tweet); // Tweet body. */
}
);
}
I am then using a script on the index.html page to post the input tweet:
<script>
$('button').on('click', (event) => {
event.preventDefault();
const tweet = $('#tweet').val();
// Post Tweet
$.ajax({
url: '/twitter/message',
method: 'POST',
data: {
tweet
}
})
.then(() => {
alert('Data successfully posted');
console.log('Data successfully posted');
})
.catch((error) => {
alert('Error: ', error);
console.log('Error: ', error);
});
})
</script>
This however is giving me the bellow error when I hit the post button:
[ { code: 32, message: 'Could not authenticate you.' } ]
If I use this exact same setup with just express it works perfectly fine, the issue occurs when trying to use react. Any help would be amazing.
It is possibly a CORS issue (which would show up in the frontend but not in Node/Backend).
If you're using some sort of API key to make the API request you're not showing it in this sample (don't show people your API key). By similar logic, do not have your API key on the client side, as anyone downloading your website would then have your Twitter API key. Instead, for multiple reasons it is better to have the backend be the one to make the API requests with your API key.
On the other hand if users are supposed to authenticate via O-Auth and you're supposed to pass a cookie with your authentication make sure you useCredentials on the request. axios.post(BASE_URL + '/api', { withCredentials: true }); . Looks like you're using jquery so add the same withCredentials:
Try adding this to your options:
crossDomain: true,
xhrFields: {
withCredentials: true
},
If you don't see a cookie when you type document.cookie in the browser that's probably a sign you're not authenticated in your computer.

Re-captcha token verification fails in AWS, but not in vercel

Below is my Next.js (backend API) code to verify recaptcha token (created from the client side) and send a mail.
import { NextApiRequest, NextApiResponse } from "next";
import NextCors from 'nextjs-cors';
import { recaptchaAxios } from "../../axios/axiosBackend";
import sendGridMail from '#sendgrid/mail';
sendGridMail.setApiKey(process.env.SENDGRID_API_KEY);
interface FormData {
contactName: string;
contactEmail: string;
contactPhone: string;
contactSubject: string;
contactMessage: string;
token: string;
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
await NextCors(req, res, {
// Options
methods: ['GET','POST'],
origin: '*',
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
});
const formData: FormData = req.body;
console.log("form Data >>>>>>>>>>>>>>",formData)
const human = await validateHuman(formData.token);
if (!human) {
res.status(400);
return res.json({ success: false, errors: ["You are not authenticated"] });
}
const message = {
to: process.env.SENDGRID_MAIL_RECEIVER,
from: process.env.SENDGRID_MAIL_SENDER, // Change to your verified sender
subject: formData.contactSubject,
text: `Name: ${formData.contactName}\n
Contact: ${formData.contactPhone} \n
Email: ${formData.contactEmail} \n
Message: ${formData.contactMessage}`,
html: `Name: ${formData.contactName}
Contact: ${formData.contactPhone}
Email: ${formData.contactEmail}
Message: ${formData.contactMessage}`,
}
try {
await sendGridMail.send(message);
res.status(200);
return res.json({ success: true, errors: [] });
} catch (error) {
console.log(error);
res.status(500);
return res.json({ success: false, errors: ['Error occured while trying to send your details. Please contact your Administrator.']});
}
};
async function validateHuman(token: string): Promise<boolean> {
const secret = process.env.RECAPTCHA_SECRET_KEY;
const response = await recaptchaAxios.post(`/siteverify?secret=${secret}&response=${token}`,{}, {});
const success = response.data['success'];
console.log("server siteverify >>>>>>>>>>>>>",response);
return success;
}
recaptchaAxios has the baseURL as below
const recaptchaAxios = axios.create({
baseURL: `https://www.google.com/recaptcha/api`,
});
I have deployed the same code in vercel as well as using AWS Amplify.
In vercel when called to the above mail API, the Recaptcha token is verified and the mail is sent.
But unfortunately in AWS it gives the error
{ success: false, errors: ["You are not authenticated"] }
I have added all the environment variables in AWS which I have in vercel and the values are the same.
All the domains are added in reCaptch v3 console for the site.
So at this point I am stuck on why in AWS gives the error, but not vercel for the same code base
Is there anything that I am missing in AWS??
Cheers
My first pointer would be to console.log the environment variables on script load, also each time the recaptcha validation is triggered. This way you can be sure the ENV vars are all loaded correctly. You would be suprised to have a small case sensitivity typo, leave you without an important env variable.
Otherwise, I would check if I need to allow outgoing traffic (firewall rules) on AWS amplify, but this is less common, since AWS Amplify spawns a public site.
Issue was in the below code
const secret = process.env.RECAPTCHA_SECRET_KEY;
Even though the RECAPTCHA_SECRET_KEY was available in the environment variables in AWS, it was not accessible.
Fix was to introduce this key in next.config.js file
module.exports = {
images: {
domains: [],
},
env: {
RECAPTCHA_SECRET_KEY: process.env.RECAPTCHA_SECRET_KEY,
},
};
This solved the problem

How do I solve CORS issues in Apollo, Node, and Next.js?

I need help troubleshooting I CORS error I am having in Apollo, Node, and Next.js. I am not sure what change I have made, but suddenly I am unable to fetch the data from my Prisma database. I am currently running dev mode. My Yoga server which pulls in my data from Prisma run at localhost:4444. My frontend is run on localhost:7777.
Here is my CORS setup:
import withApollo from "next-with-apollo";
import ApolloClient from "apollo-boost";
import { endpoint, prodEndpoint } from "../config";
import { LOCAL_STATE_QUERY } from "../components/Cart";
function createClient({ headers }) {
return new ApolloClient({
uri: process.env.NODE_ENV === "development" ? endpoint : prodEndpoint,
request: (operation) => {
operation.setContext({
fetchOptions: {
credentials: "include",
},
headers,
});
},
// local data
clientState: {
resolvers: {
Mutation: {
toggleCart(_, variables, { cache }) {
// read the cartOpen value from the cache
const { cartOpen } = cache.readQuery({
query: LOCAL_STATE_QUERY,
});
// Write the cart State to the opposite
const data = {
data: { cartOpen: !cartOpen },
};
cache.writeData(data);
return data;
},
},
},
defaults: {
cartOpen: false,
},
},
});
}
export default withApollo(createClient);
variables.env
FRONTEND_URL="localhost:7777"
PRISMA_ENDPOINT="https://us1.prisma.sh/tim-smith-131869/vouch4vet_dev_backend/dev"
PRISMA_SECRET="..."
APP_SECRET="..."
STRIPE_SECRET="..."
PORT=4444
backend index.js
const server = createServer();
server.express.use(cookieParser());
// decode the JWT so we can get the user Id on each request
server.express.use((req, res, next) => {
const { token } = req.cookies;
if (token) {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
// put the userId onto the req for future requests to access
req.userId = userId;
}
next();
});
I have tried rolling back to previous commit and I have had no luck. I have not ruled out internet problems.
Let me know if you need to see the rest of my repo.
Thanks

node.js request stuck in middleware function (authentication)?

I am building a node.js server to handle logins and actions in my iOS app. One of the functions I wrote is checkAuth, which is middleware for most of my requests to check if a user is authenticated and has permission to do what he wants to do.
Now I am facing a problem where sometimes, but not always, the middleware function (checkAuth) is stuck. I receive logs from this function in my console, but nothing from the request (which should happen after authentication is successful).
This is the function I currently wrote. It is not optimized yet as I am testing everything, but it should do what I want it to do (and it does, most of the time):
const saltRounds = process.env.JWT_ROUNDS
const secret = process.env.JWT_SECRET
const checkRefreshTime = 10 // set to 10 seconds for testing, will increase later
function checkAuth(req, res, next) {
var token = req.headers['x-access-token']
jwt.verify(token, secret, (error, decoded) => {
console.log("checking auth")
if(error) {
console.log(error)
res.json({ errorCode: 406 })
} else {
const decoded = jwt.verify(token, secret)
var checkTime = (Date.now() / 1000) - checkRefreshTime;
if (decoded.iat < checkTime) {
console.log("DEC:", decoded)
const userID = decoded.userID
const queryString = "SELECT userRefreshToken, userName, userDisplayName, userProfilePicURL, userDOB, userGender FROM users WHERE userID = ? LIMIT 1"
pool.getConnection(function(error, connection) {
if(error) {
console.log(error)
res.json({ errorCode: 500 })
}
connection.query(queryString, [userID], (error, selectRows, fields) => {
if(error) {
console.log(error)
res.json({ errorCode: 500 })
}
if(selectRows.length > 0) {
if(selectRows[0].userRefreshToken == decoded.userRefreshToken) {
var userAge = moment().diff(selectRows[0].userDOB, 'years');
const payload = {
userID: userID,
userName: selectRows[0].userName,
userDisplayName: selectRows[0].userDisplayName,
userProfilePicURL: selectRows[0].userProfilePicURL,
userRefreshToken: selectRows[0].userRefreshToken,
userAge: userAge,
userGender: selectRows[0].userGender
}
var newToken = jwt.sign(payload, secret, { expiresIn: '21d' });
console.log("new token sent ", newToken)
res.locals.authToken = newToken
console.log("moving to next")
return next()
} else {
console.log("wrong refresh token")
res.json({ errorCode: 405, authResult: false })
}
} else {
console.log("0 results found!")
res.json({ errorCode: 503, authResult: false })
}
connection.release()
})
})
} else {
console.log("moving to next 2")
return next()
}
}
})
}
It probably isn't the most beautiful code you have ever seen. That's not my issue at this moment - I will optimize at a later time. Right now I am concerned about the fact that sometimes the function is stuck after the second check. The last output I then receive is "DEC: " followed by the decoded token in my console (line 16).
Other useful information: I run my server on an Ubuntu 18.04 server from DigitalOcean and use forever to keep it running:
forever start --minUptime 1000 --spinSleepTime 1000 server.js
Anybody who knows why this is happening?
EDIT: as per comment, the definition of pool
var pool = mysql.createPool({
connectionLimit: 100,
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_BASE,
ssl : {
ca : fs.readFileSync('***********'),
key : fs.readFileSync('*********'),
cert : fs.readFileSync('********'),
},
charset : 'utf8mb4',
dateStrings: true
})
I don't see where pool is defined anywhere. That could be throwing an error in the server logs.
Put a console log in the connection function to check that it actually connected to mySQL since that is the next function in the chain.

NodeJS SOAP client request with certificate

I'm trying to connect to a SOAP service which requires a certificate on my NodeJS project.
I'm using soap and this is how I'm trying to access to it:
const soap = require('soap');
(...)
let sslOptions = {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath)
};
let sslHeaders = {
Authorization: {
user: 'Z100079',
pass: password
}
};
Service.GetLossRatio = function (contractID, start, end, asOf, cb) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let args = {
contractID: contractID,
start: start,
end: end,
asOf: asOf
};
soap.createClientAsync(url,
{
//request : specialRequest,
wsdl_options: sslOptions,
wsdl_headers: sslHeaders
})
.then((client) => {
client.setSecurity(
new soap.ClientSSLSecurityPFX(
certCerPath
)
);
client.LossratioService.LossratioPort.calculate(args, (err, res) => {
if (err) {
console.log(err);
return cb(err);
}
console.log(res);
return cb(null, res);
});
})
.catch((e) => {
console.log(e);
cb(e);
});
};
And I'm getting a "Wrong Tag" Error when the LossratioPort.calculate() occurs.
I've no idea what that error means, I can't find much documentation about this specific situation, the "soap" documentation only show a brief explenation on how to make a request with certificates but there's not much more
I know the certificates are valid and I've tried with all the generated certificates from the .pem file (.p12, and .cer). I just want to be sure I'm getting something from the service. Either what I really want, or an error from the server, not from the api.
Any help?
Thanks in advance.
UPDATE:
I'm able to get the service description thorugh client.describe() though:
{"LossratioService":{"LossratioPort":{"calculate":{"input":"tns:calculate","output":"tns:calculateResponse"},"ping":{"input":"tns:ping","output":"tns:pingResponse"},"shakedownTest":{"input":"tns:shakedownTest","output":"tns:shakedownTestResponse"}}}}
I've also confirmed the inputs, and I'm sending as the service requires.

Resources