Fastify REST-API JWT-Auth Plugin not firing as preHandler - javascript

Im setting up a Fastify Rest-Api and wrote a Plugin to encapsulate my authentication logic which is based on JWT. Im using the preHandler Hook on each route that i want to protect but it seems that the preHandler or my plugin just gets ignored since i can just make a request without a token at all and get the data.
I looked up every piece of documentation but still cannot get it running. If i just console.log() my function fastify.authenticate i get an undefined.
This is my plugin customJwtAuth:
const fp = require('fastify-plugin')
async function customJwtAuth(fastify, opts, next) {
//register jwt
await fastify.register(require('fastify-jwt'),
{secret: 'asecretthatsverylongandimportedfromanenvfile'})
fastify.decorate('authenticate', async function(request, reply) {
try {
const tokenFromRequest = request.cookies.jwt
await fastify.jwt.verify(tokenFromRequest, (err, decoded) => {
if (err) {
fastify.log.error(err)
reply.send(err)
}
fastify.log.info(`Token verified: ${decoded}`)
})
} catch (err) {
reply.send(err)
fastify.log.error(err)
}
})
next()
}
module.exports = fp(customJwtAuth, {fastify: '>=1.0.0'})
I register this plugin like this in my main server.js file:
const customJwtAuth = require('./plugin/auth')
fastify.register(customJwtAuth).after(err => {if (err) throw err})
Then i apply my function like this to the routes:
const fastify = require('fastify')
const productHandler = require('../handler/productHandler')
const productRoutes = [
{
method: 'GET',
url: '/api/product',
preHandler: [fastify.authenticate],
handler: productHandler.getProducts
}, ... ]
The api shouldnt return any Data if the request doesnt include a signed jwt or without a jwt at all.

here to you a working example.
Note that you were calling next() when you were registering the decorator that is wrong.
Your main error was due the [fastify.authenticate] line, because you don't have the decorator in that fastify instance.
//### customAuthJwt.js
const fastifyJwt = require('fastify-jwt')
const fp = require('fastify-plugin')
async function customJwtAuth(fastify, opts, next) {
fastify.register(fastifyJwt, { secret: 'asecretthatsverylongandimportedfromanenvfile' })
fastify.decorate('authenticate', async function (request, reply) {
try {
// to whatever you want, read the token from cookies for example..
const token = request.headers.authorization
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
}
module.exports = fp(customJwtAuth, { fastify: '>=1.0.0' })
//### server.js
const fastify = require('fastify')({ logger: true })
const customJwtAuth = require('./customAuthJwt')
fastify.register(customJwtAuth)
fastify.get('/signup', (req, reply) => {
// authenticate the user.. are valid the credentials?
const token = fastify.jwt.sign({ hello: 'world' })
reply.send({ token })
})
fastify.register(async function (fastify, opts) {
fastify.addHook('onRequest', fastify.authenticate)
fastify.get('/', async function (request) {
return 'hi'
})
})
fastify.listen(3000)
You get:
curl http://localhost:3000/
{"statusCode":401,"error":"Unauthorized","message":"No Authorization was found in request.headers"}
curl http://localhost:3000/signup
{"token": "eyJhbGciOiJIUzI1NiI..."}
curl 'http://localhost:3000/' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiI...'
hi

if you're using version 2 of fastify you can use PreHandler, if not you need to user beforeHandler
And also, you need to change the routes for something like this
//routes/products.js
const fastify = require('fastify')
const productHandler = require('../handler/productHandler')
module.exports = function (fastify, opts, next) {
fastify.route({
method: 'GET',
url: 'api/product',
beforeHandler: fastify.auth([
fastify.authenticate
]),
handler: productHandler.getProducts
})
......
next()
}
//server.js
....
fastify.register(require('fastify-auth'))
.register(customJwtAuth)
const customJwtAuth = require('./customAuthJwt')
....
fastify.register(
require('./routes/products')
)

Related

How to convert lambda function into a next.js api friendly function?

I have a Instagram api making calls to my personal profile and ran as a lambda function from netlify which goes like this:
require('isomorphic-unfetch')
const url = `https://www.instagram.com/graphql/query/...`
async function getPosts() {...}
exports.handler = async function (event, context, callback) {
const posts = await getPosts()
callback(null, {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(posts),
})
}
I am rebuilding the site using Next.js and migrating my site to vercel and want to run the serverless function from /pages/api/insta.js but Next.js throws an error as it expects something along the lines of :
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
// export default (req, res) => {
// res.statusCode = 200
// res.json({ name: 'John Doe' })
// }
Ok lads, managed to resolve the issue by conversion to the following code - posting it in case someone else comes across the same issue:
export default async function handler(req, res) {
const posts = await getPosts()
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(posts))
}

How to make call to external api from nodejs

Hi all I have to develop a utility which makes a call to external API with different parameters, for example, I have an array val which has 100 value val= ['we23','22ww', 'gh22'....n] and URL: www.google.com so one by one I have to append value from val to the URL, first api= www.google.com/we23, second api= www.google.com/22ww and make an External API hit and then store the response in database. so what is the most efficient way to do it? and links to working examples would be helpful.
A very simple example express app using the Fetch API:
const express = require('express')
const fetch = require('node-fetch')
const app = express()
// This sets up a route to localhost:3000/random and goes off and hits
// cat-fact.herokuapp.com/facts/random
app.get('/:apiRoute', async (req, res) => {
try {
const { apiRoute } = req.params
const apiResponse = await fetch(
'https://cat-fact.herokuapp.com/facts/' + apiRoute
)
const apiResponseJson = await apiResponse.json()
// await db.collection('collection').insertOne(apiResponseJson)
console.log(apiResponseJson)
res.send('Done – check console log')
} catch (err) {
console.log(err)
res.status(500).send('Something went wrong')
}
})
app.listen(3000, () => console.log(`Example app listening on port 3000!`))
Visit http://localhost:3000/random
With the following code you can make concurrent API calls within an endpoint using Node.js + Express:
const [
LoMasNuevo, LoMasVisto, TeRecomendamos, Categorias,
] = await Promise.all([
numerosController.getLoMasNuevo(),
numerosController.getLoMasVisto(),
numerosController.getRecomendaciones(),
categoriasController.getCategorias(),
]);
Inside every get function you can make an axios request like this:
const params = {
method: 'GET',
url: 'https://development.api.yodlee.com/ysl/transactions',
headers: {
'Api-Version': '1.1',
Authorization: `Bearer ${tokenuser}`,
},
};
const data = await axios(params);
return data;
In 2022
In Node.js:
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) =>
fetch(...args));
app.get('/checkDobleAPI', async (req, res) => {
try {
const apiResponse = await fetch(
'https://jsonplaceholder.typicode.com/posts'
)
const apiResponseJson = await apiResponse.json()
console.log(apiResponseJson)
res.send('Running 🏃')
} catch (err) {
console.log(err)
res.status(500).send('Something went wrong')
}
})
You can use Express to build a API as your idea
Then you can call api by using axios package.
In addition, you can build link to receive request and send response by using Router of ExpressJS

Trying to get axios-auth-refresh working with NodeJS

I'm trying to get axios-auth-refresh working, to ensure that my requests always have a valid auth token, but as far as I can tell the Axios request isn't continuing after axios-auth-refresh intercepts it.
I'm pretty new to JS development, so not sure if I've missed something obvious. I've looked through the documentation, but can't see any major differences in my implementation.
I'm running Node v13.2.0, v2.2 (latest) of axios-auth-refresh, and v0.18.1 of axios
My code is as follows:
require('axios-debug-log');
const axios = require('axios');
const axiosauthrefresh = require('axios-auth-refresh');
const instance = axios.create({
baseURL: 'https://api.example.com/api/v1.0',
});
let authToken = '';
const refreshAuthLogic = (failedRequest) => {
console.log('Intercepting auth');
instance
.post('/auth/login/', {
username: process.env.USER,
password: process.env.PASS,
skipAuthRefresh: true,
})
.then((tokenRefreshResponse) => {
authToken = tokenRefreshResponse.data.token;
failedRequest.response.config.headers.Authorization = `Token ${authToken}`;
console.log(`Auth token: ${authToken}`);
return Promise.resolve();
});
};
function getAuthToken() {
if (authToken) {
console.log(`Token exists: ${authToken}`);
return `Token ${authToken}`;
}
return null;
}
instance.interceptors.request.use((request) => {
console.log(`Requesting ${request.url}`);
const token = getAuthToken();
if (token) {
request.headers.Authorization = token;
}
return request;
});
axiosauthrefresh.default(instance, refreshAuthLogic);
module.exports = {
instance,
};
I make a request like this:
// nmcapi.js
const request= require('./request');
async function GetFolderInfo(volumeID, filerID, path) {
try {
const refreshResponse = await request.instance.get(`/volumes/${volumeID}/filers/${filerID}/path/${path}`);
console.log(`Refresh triggered: ${path}`);
} catch (error) {
console.log(error);
}
}
// interval.js
const nmcapi = require('./nmcapi.js');
const info = await GetFolderInfo('examplevolumeid', 'examplefilerid', 'examplepath')
And this is what I get as output:
Requesting /volumes/examplevolumeid/filers/examplefilerid/path/examplepath
axios GET /volumes/examplevolumeid/filers/examplefilerid/path/examplepath +1ms
axios Error: Request failed with status code 401 (GET https://api.example.com/api/v1.0/volumes/examplevolumeid/filers/examplefilerid/path/examplepath) +265ms
Intercepting auth
Requesting /auth/login/
TypeError: Cannot read property 'then' of undefined
at f (/home/sean/data-reports/node_modules/axios-auth-refresh/dist/index.min.js:1:1718)
at /home/sean/data-reports/node_modules/axios-auth-refresh/dist/index.min.js:1:2719
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at Object.GetFolderInfo (/home/sean/data-reports/server/nmcapi.js:29:29)
at /home/sean/data-reports/server/interval.js:25:18
at async Promise.all (index 0)
at Object.intervalFunc (/home/sean/data-reports/server/interval.js:36:18)
axios POST /auth/login/ +16ms
axios 200 OK (POST https://api.example.com/api/v1.0/auth/login/) +561ms
Auth token: 17412724ef5169eaab8502a9851480741e606ffa
As far as I can tell, the refreshAuthLogic function is working properly (because it returns a new auth token), but everything stops after that.
What am I missing?
I had missed an important point, the refreshAuthLogic function actually needs to return the axios instance instance.
The working implementation is:
const refreshAuthLogic = (failedRequest) => {
return instance
.post('/auth/login/', {
username: process.env.USER,
password: process.env.PASS,
skipAuthRefresh: true,
})
.then((tokenRefreshResponse) => {
failedRequest.response.config.headers.Authorization = `Token ${tokenRefreshResponse.data.token}`;
return Promise.resolve();
});
};
Thanks to Flyrell for answering this on GitHub

FeathersJS: Injecting HTTP headers in Service Test

In a feathersJS service, I have a before hook being ran that expects a certain HTTP header to exist:
src/services/service_name/service_name.hooks.js
const validationHook = () => (context, next) => {
if (!context.params.headers.hasOwnProperty('header-wanted'))
throw new errors.BadRequest();
next(null, context);
};
module.exports = {
before: {
all: [cronValidationHook()],
...
..
.
When testing this service in a generated test file from feathers-cli, however, I haven't found a way to inject headers prior to the before hook being called. The test in question is:
test/services/service_name.test.js
describe('get', () => {
it('should run "id" endpoint', async () => {
const service = app.service('v1/cron');
const resp = await service.get('id', params);
// Assertions exist after this call
});
});
Is there a way to do this that does not require utilizing an HTTP call via node-fetch or requests?
params will be whatever you pass. Just set params.headers to what you would like to test, e.g.
const getParams = {
...params,
headers: { 'header-wanted': 'something' }
};
const resp = await service.get('id', getParams);

How to send message in DELETE route - express.js

I want to show message after deleting user but I don't know how to do it. I tried to create req.session properties and then use them but they are not available in GET route. Do you know how to fix this code?
router.get("/", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
let currentMessage = req.session.message;
let currentState = req.session.state;
req.session.message = undefined;
req.session.state = undefined;
console.log(currentState, currentMessage); //undefined
user.getAll()
.then(result => {
res.render("users", {
name: req.user,
users: result,
msg: currentMessage,
state: currentState
})
})
});
// delete route
router.delete("/delete/:id", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
user.del(req.params.id)
.then(() => {
req.session.message = "Some message!"
req.session.state = true;
})
});
// jquery
function ajaxDelete(ev, url) {
ev.preventDefault();
$.ajax({
url: url,
type: "DELETE"
});
}
delBtn.click(function(e) {
var user = $(this).data("user");
ajaxDelete(e, "/users/delete/" + user);
window.location.href = "/users";
})
Use res parameter, and make a variable called message
const message= 'MyMessage';
then
res.json ({message}) // es6 feature
output
{"message":"myMessage"}
In your scenario, as far as I understand you want to send the JSON in response. You can use this code
router.delete("/delete/:id", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
user.del(req.params.id)
.then(() => {
var response = { message : "Some message!",
state : true };
return res.json(response);
})
});
the keyword 'return' is as per your requirement
router and session are middleware to any nodeJs App,If the router is added before session like this:
app.use(router)
app.use(session(...));
Then the session middleware won't get called for any requests that get handled by router.
Hence change the order of adding router and session middleware,like this
app.use(session(...));
app.use(router)

Categories